---
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
---
## What are DOM Components?
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.
## When to Use DOM Components
Use DOM components when you need:
- **Web-only libraries** — Charts (recharts, chart.js), syntax highlighters, rich text editors, or any library that depends on DOM APIs
- **Migrating web code** — Bring existing React web components to native without rewriting
- **Complex HTML/CSS layouts** — When CSS features aren't available in React Native
- **iframes or embeds** — Embedding external content that requires a browser context
- **Canvas or WebGL** — Web graphics APIs not available natively
## When NOT to Use DOM Components
Avoid DOM components when:
- **Native performance is critical** — Webviews add overhead
- **Simple UI** — React Native components are more efficient for basic layouts
- **Deep native integration** — Use local modules instead for native APIs
- **Layout routes** — `_layout` files cannot be DOM components
## Basic DOM Component
Create a new file with the `'use dom';` directive at the top:
```tsx
// components/WebChart.tsx
"use dom";
export default function WebChart({
data,
}: {
data: number[];
dom: import("expo/dom").DOMProps;
}) {
return (
Chart Data
{data.map((value, i) => (
{value}
))}
);
}
```
## Rules for DOM Components
1. **Must have `'use dom';` directive** at the top of the file
2. **Single default export** — One React component per file
3. **Own file** — Cannot be defined inline or combined with native components
4. **Serializable props only** — Strings, numbers, booleans, arrays, plain objects
5. **Include CSS in the component file** — DOM components run in isolated context
## The `dom` Prop
Every DOM component receives a special `dom` prop for webview configuration. Always type it in your props:
```tsx
"use dom";
interface Props {
content: string;
dom: import("expo/dom").DOMProps;
}
export default function MyComponent({ content }: Props) {
return
{content}
;
}
```
### Common `dom` Prop Options
```tsx
// Disable body scrolling
// Flow under the notch (disable safe area insets)
// Control size manually
// Combine options
```
## Exposing Native Actions to the Webview
Pass async functions as props to expose native functionality to the DOM component:
```tsx
// app/index.tsx (native)
import { Alert } from "react-native";
import DOMComponent from "@/components/dom-component";
export default function Screen() {
return (
{
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 };
}}
/>
);
}
```
```tsx
// components/dom-component.tsx
"use dom";
interface Props {
showAlert: (message: string) => Promise;
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 ;
}
```
## Using Web Libraries
DOM components can use any web library:
```tsx
// components/syntax-highlight.tsx
"use dom";
import SyntaxHighlighter from "react-syntax-highlighter";
import { docco } from "react-syntax-highlighter/dist/esm/styles/hljs";
interface Props {
code: string;
language: string;
dom?: import("expo/dom").DOMProps;
}
export default function SyntaxHighlight({ code, language }: Props) {
return (
{code}
);
}
```
```tsx
// 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 (
);
}
```
## CSS in DOM Components
CSS imports must be in the DOM component file since they run in isolated context:
```tsx
// 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 (
);
}
```
## Expo Router in DOM Components
The expo-router `` component and router API work inside DOM components:
```tsx
"use dom";
import { Link, useRouter } from "expo-router";
export default function Navigation({
dom,
}: {
dom: import("expo/dom").DOMProps;
}) {
const router = useRouter();
return (
);
}
```
### Router APIs That Require Props
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:
```tsx
// 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 ;
}
```
```tsx
// 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 (
Current ID: {id}
Current Path: {pathname}
);
}
```
## Detecting DOM Environment
Check if code is running in a DOM component:
```tsx
"use dom";
import { IS_DOM } from "expo/dom";
export default function Component({
dom,
}: {
dom?: import("expo/dom").DOMProps;
}) {
return
{IS_DOM ? "Running in DOM component" : "Running natively"}
;
}
```
## Assets
Prefer requiring assets instead of using the public directory:
```tsx
"use dom";
// Good - bundled with the component
const logo = require("../assets/logo.png");
export default function Component({
dom,
}: {
dom: import("expo/dom").DOMProps;
}) {
return ;
}
```
## Usage from Native Components
Import and use DOM components like regular components:
```tsx
// app/index.tsx
import { View, Text } from "react-native";
import WebChart from "@/components/web-chart";
import CodeBlock from "@/components/code-block";
export default function HomeScreen() {
return (
Native content aboveNative content below
);
}
```
## Platform Behavior
| 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.
## Tips
- DOM components hot reload during development
- Keep DOM components focused — don't put entire screens in webviews
- Use native components for navigation chrome, DOM components for specialized content
- Test on all platforms — web rendering may differ slightly from native webviews
- Large DOM components may impact performance — profile if needed
- The webview has its own JavaScript context — cannot directly share state with native