Back to Expo & Mobile

use-dom

ExpoReact NativeWebviewHybrid AppDOMCross-PlatformCode MigrationWeb-only Libraries
⭐ 2.1kπŸ“„ MITπŸ•’ 2026-06-16Source β†—

Install this skill

npx skills add expo/skills

Works across Claude Code, Cursor, Codex, Copilot & Antigravity

The 'use dom' directive transforms Expo files into isolated webview-based components. By including this directive at the top of a file, developers can render standard HTML, CSS, and web-only React libraries directly within a native mobile application. This creates a bridge that executes web-specific code in a browser context while maintaining a consistent React API. It acts as an escape hatch for requirements that fall outside the capabilities of native UI primitives, such as specialized data visualizations or legacy web interfaces. Communication between the native host and the webview occurs via props, where asynchronous functions allow the web environment to trigger native system alerts or local storage operations, keeping the logic modular and contained within its own runtime environment.

When to Use This Skill

  • β€’Integrating complex charting libraries like Recharts or D3.js
  • β€’Embedding rich text editors or syntax highlighting components
  • β€’Reusing existing React web project components without modification
  • β€’Rendering dynamic content requiring browser-specific HTML/CSS features

How to Invoke This Skill

Example prompts that trigger this skill in Claude Code, Cursor, or Antigravity:

  • β€œHow do I use a web-only library in Expo?
  • β€œRender HTML or CSS components inside my native app
  • β€œUse the use dom directive for webviews
  • β€œBridge native functions to a webview component
  • β€œCreate a component that runs in a webview context

Pro Tips

  • πŸ’‘Prioritize performance-critical sections for native React Native components, using DOM components judiciously for less demanding, web-dependent features to balance performance and development speed.
  • πŸ’‘When migrating, start with self-contained web components that have minimal native dependencies, gradually integrating them to identify and address any webview-specific quirks early.
  • πŸ’‘Ensure robust cross-platform testing. While DOM components aim for 'as-is' rendering, differences in webview implementations across platforms (iOS vs. Android) can sometimes necessitate minor adjustments.

What this skill does

  • β€’Execute arbitrary web-only React libraries and components natively
  • β€’Isolate web-based rendering in a controlled browser context
  • β€’Bridge native host logic to webview components via async props
  • β€’Configure viewport behavior like scrolling and safe area insets via the dom prop
  • β€’Maintain native-like component syntax for hybrid applications

When not to use it

  • βœ•When high-performance animations or deep native UI integration are required
  • βœ•When standard React Native core components can fulfill the UI requirement
  • βœ•For top-level layout routing structures

Example workflow

  1. Add 'use dom'; to the top of your component file
  2. Define your component using standard HTML tags and web libraries
  3. Import the DOMProps type to handle specific webview configurations
  4. Pass native functions as props to the component for bridge communication
  5. Include the component in your app's view hierarchy like a standard component

Prerequisites

  • –Expo project environment
  • –Basic understanding of React web development

Pitfalls & limitations

  • !Webview overhead can impact performance compared to native components
  • !Components must reside in their own files and cannot be defined inline
  • !Props passed to the component must be serializable
  • !Cannot use layout route files as DOM components

FAQ

Does 'use dom' work on all platforms?
It targets native platforms by using a webview and behaves as a standard component on web.
Can I pass complex objects to a DOM component?
Only serializable data like strings, numbers, booleans, and plain objects are supported; complex class instances or non-serializable objects will fail.
How do I trigger native alerts from the webview?
Pass an async function from your native component as a prop to your DOM component, then call it from within your web code.
Is it possible to use multiple components in one 'use dom' file?
No, each file must contain exactly one default export representing the DOM component.

How it compares

Unlike standard iframes or manual webview wrappers, 'use dom' provides a native React component interface, allowing for type-safe prop passing and native-to-web communication without manual message event listeners.

Source & trust

⭐ 2.1k starsπŸ“„ MITπŸ•’ Updated 2026-06-16
πŸ“„ Full skill instructions β€” original source: expo/skills
## 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:

// 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>
);
}


## 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:

"use dom";

interface Props {
content: string;
dom: import("expo/dom").DOMProps;
}

export default function MyComponent({ content }: Props) {
return <div>{content}</div>;
}


### Common 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 }
}}
/>


## Exposing Native Actions to the Webview

Pass async functions as props to expose native functionality to the DOM component:

// app/index.tsx (native)
import { Alert } from "react-native";
import DOMComponent from "@/components/dom-component";

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>;
}


## Using Web Libraries

DOM components can use any web library:

// 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 (
<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 in DOM Components

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>
);
}


## Expo Router in DOM Components

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>
);
}


### 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:

// 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>
);
}


## Detecting DOM Environment

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>;
}


## Assets

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" />;
}


## Usage from Native Components

Import and use DOM components like regular components:

// 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 (
<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

| 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

How to Use This Skill Unit

Option A: Project-Specific (Recommended)

  1. Click "Download" above
  2. In your project, create the directory: .agent/skills/use-dom/
  3. Save the file as SKILL.md
  4. The agent will automatically discover the skill based on its description.

Option B: Global Installation (All Agents)

Save the file to these locations to make it available across all projects:

  • Claude Code: ~/.claude/skills/expo/skills/use-dom/SKILL.md
  • Cursor: ~/.cursor/skills/expo/skills/use-dom/SKILL.md
  • Antigravity: ~/.gemini/antigravity/skills/expo/skills/use-dom/SKILL.md

πŸš€ Install with CLI:
npx skills add expo/skills

Read the Master Guide: Mastering Agent Skills β†’

Recommended Rules

View more rules β†’

Recommended Workflows

View more workflows β†’

Recommended MCP Servers

View more MCP servers β†’

Take It Further

Maximize your productivity with these powerful resources

πŸ“‹

Define Your Standards

Set up coding standards to ensure this workflow produces consistent, high-quality results.

Browse Rules Library
πŸ“–

Master Workflows

Learn how to create custom workflows, use Turbo Mode, and build your automation library.

Complete Guide

How to use this Skill in Claude Code & Cursor

For Claude Code (CLI)

To use this skill in Claude Code, copy the rule content into your project's custom instructions or follow our Add-Skill CLI guide. This ensures Claude follows your standards during every code generation.

For Cursor & Windsurf

For Cursor or Windsurf, individual skills are best used in the "Rules for AI" section. This specific unit helps the agent avoid expo & mobile issues, leading to cleaner, more efficient code.

Why the skill format matters: the standardized Agent Skills format lets your AI agent load detailed instructions only when they are relevant, keeping your prompt clean while improving results.

Source & attribution

This skill is categorized under Expo & Mobile and is published by Expo Team, maintained in expo/skills.

← Browse All Agent Skills
Sponsored AI assistant. Recommendations may be paid.