원클릭으로
use-dom
Use Expo DOM components to run web code in a webview on native and as-is on web. Migrate web code to native incrementally.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Use Expo DOM components to run web code in a webview on native and as-is on web. Migrate web code to native incrementally.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | use-dom |
| description | Use Expo DOM components to run web code in a webview on native and as-is on web. Migrate web code to native incrementally. |
| version | 1.0.0 |
| license | MIT |
DOM components allow web code to run verbatim in a webview on native platforms while rendering as-is on web. This enables using web-only libraries like recharts, react-syntax-highlighter, or any React web library in your Expo app without modification.
Use DOM components when you need:
Avoid DOM components when:
_layout files cannot be DOM componentsCreate a new file with the 'use dom'; directive at the top:
// components/WebChart.tsx
'use dom'
export default function WebChart({
data,
}: {
data: number[]
dom: import('expo/dom').DOMProps
}) {
return (
<div style={{padding: 20}}>
<h2>Chart Data</h2>
<ul>
{data.map((value, i) => (
<li key={i}>{value}</li>
))}
</ul>
</div>
)
}
'use dom'; directive at the top of the filedom PropEvery DOM component receives a special dom prop for webview configuration. Always type it in your props:
'use dom'
interface Props {
content: string
dom: import('expo/dom').DOMProps
}
export default function MyComponent({content}: Props) {
return <div>{content}</div>
}
dom Prop Options// Disable body scrolling
<DOMComponent dom={{ scrollEnabled: false }} />
// Flow under the notch (disable safe area insets)
<DOMComponent dom={{ contentInsetAdjustmentBehavior: "never" }} />
// Control size manually
<DOMComponent dom={{ style: { width: 300, height: 400 } }} />
// Combine options
<DOMComponent
dom={{
scrollEnabled: false,
contentInsetAdjustmentBehavior: "never",
style: { width: '100%', height: 500 }
}}
/>
Pass async functions as props to expose native functionality to the DOM component:
// app/index.tsx (native)
import DOMComponent from '@/components/dom-component'
import {Alert} from 'react-native'
export default function Screen() {
return (
<DOMComponent
showAlert={async (message: string) => {
Alert.alert('From Web', message)
}}
saveData={async (data: {name: string; value: number}) => {
// Save to native storage, database, etc.
console.log('Saving:', data)
return {success: true}
}}
/>
)
}
// components/dom-component.tsx
'use dom'
interface Props {
showAlert: (message: string) => Promise<void>
saveData: (data: {name: string; value: number}) => Promise<{success: boolean}>
dom?: import('expo/dom').DOMProps
}
export default function DOMComponent({showAlert, saveData}: Props) {
const handleClick = async () => {
await showAlert('Hello from the webview!')
const result = await saveData({name: 'test', value: 42})
console.log('Save result:', result)
}
return <button onClick={handleClick}>Trigger Native Action</button>
}
DOM components can use any web library:
// components/syntax-highlight.tsx
'use dom'
import {docco} from 'react-syntax-highlighter/dist/esm/styles/hljs'
import SyntaxHighlighter from 'react-syntax-highlighter'
interface Props {
code: string
language: string
dom?: import('expo/dom').DOMProps
}
export default function SyntaxHighlight({code, language}: Props) {
return (
<SyntaxHighlighter
language={language}
style={docco}
>
{code}
</SyntaxHighlighter>
)
}
// components/chart.tsx
'use dom'
import {LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip} from 'recharts'
interface Props {
data: Array<{name: string; value: number}>
dom: import('expo/dom').DOMProps
}
export default function Chart({data}: Props) {
return (
<LineChart
width={400}
height={300}
data={data}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Line
type="monotone"
dataKey="value"
stroke="#8884d8"
/>
</LineChart>
)
}
CSS imports must be in the DOM component file since they run in isolated context:
// components/styled-component.tsx
'use dom'
import '@/styles.css' // CSS file in same directory
export default function StyledComponent({
dom,
}: {
dom: import('expo/dom').DOMProps
}) {
return (
<div className="container">
<h1 className="title">Styled Content</h1>
</div>
)
}
Or use inline styles / CSS-in-JS:
'use dom'
const styles = {
container: {
padding: 20,
backgroundColor: '#f0f0f0',
},
title: {
fontSize: 24,
color: '#333',
},
}
export default function StyledComponent({
dom,
}: {
dom: import('expo/dom').DOMProps
}) {
return (
<div style={styles.container}>
<h1 style={styles.title}>Styled Content</h1>
</div>
)
}
The expo-router <Link /> component and router API work inside DOM components:
'use dom'
import {Link, useRouter} from 'expo-router'
export default function Navigation({dom}: {dom: import('expo/dom').DOMProps}) {
const router = useRouter()
return (
<nav>
<Link href="/about">About</Link>
<button onClick={() => router.push('/settings')}>Settings</button>
</nav>
)
}
These hooks don't work directly in DOM components because they need synchronous access to native routing state:
useLocalSearchParams()useGlobalSearchParams()usePathname()useSegments()useRootNavigation()useRootNavigationState()Solution: Read these values in the native parent and pass as props:
// app/[id].tsx (native)
import {useLocalSearchParams, usePathname} from 'expo-router'
import DOMComponent from '@/components/dom-component'
export default function Screen() {
const {id} = useLocalSearchParams()
const pathname = usePathname()
return (
<DOMComponent
id={id as string}
pathname={pathname}
/>
)
}
// components/dom-component.tsx
'use dom'
interface Props {
id: string
pathname: string
dom?: import('expo/dom').DOMProps
}
export default function DOMComponent({id, pathname}: Props) {
return (
<div>
<p>Current ID: {id}</p>
<p>Current Path: {pathname}</p>
</div>
)
}
Check if code is running in a DOM component:
'use dom'
import {IS_DOM} from 'expo/dom'
export default function Component({dom}: {dom?: import('expo/dom').DOMProps}) {
return <div>{IS_DOM ? 'Running in DOM component' : 'Running natively'}</div>
}
Prefer requiring assets instead of using the public directory:
'use dom'
// Good - bundled with the component
const logo = require('../assets/logo.png')
export default function Component({dom}: {dom: import('expo/dom').DOMProps}) {
return (
<img
src={logo}
alt="Logo"
/>
)
}
Import and use DOM components like regular components:
// app/index.tsx
import CodeBlock from '@/components/code-block'
import WebChart from '@/components/web-chart'
import {View, Text} from 'react-native'
export default function HomeScreen() {
return (
<View style={{flex: 1}}>
<Text>Native content above</Text>
<WebChart
data={[10, 20, 30, 40, 50]}
dom={{style: {height: 300}}}
/>
<CodeBlock
code="const x = 1;"
language="javascript"
dom={{scrollEnabled: true}}
/>
<Text>Native content below</Text>
</View>
)
}
| Platform | Behavior |
|---|---|
| iOS | Rendered in WKWebView |
| Android | Rendered in WebView |
| Web | Rendered as-is (no webview wrapper) |
On web, the dom prop is ignored since no webview is needed.
Complete guide for building beautiful apps with Expo Router. Covers fundamentals, styling, components, navigation, animations, patterns, and native tabs.
Set up Tailwind CSS v4 in Expo with react-native-css and NativeWind v5 for universal styling
Add an iOS App Clip target to an Expo app. Use when the user mentions App Clip, AASA, apple-app-site-association, appclips, smart app banner, or wants to ship a lightweight iOS Clip invoked from a URL alongside their parent app.
Check the health of published EAS Updates: crash rates, install/launch counts, unique users, payload size, and the split between embedded and OTA users per channel. Use when the user asks how an update is performing, whether a rollout is healthy, how many users are on the embedded build vs OTA, or wants to gate CI on update health.
Guidelines for creating API routes in Expo Router with EAS Hosting
Integrate Expo and React Native into an existing native iOS or Android app. Use when the user mentions brownfield, embedding React Native in a native app, AAR/XCFramework, or adding Expo to an existing Kotlin/Swift project. Covers both the isolated approach and the integrated approach.