Use Dom
OpenAIOfficialCreated by Expo TeamDistributed by OpenAI
- Skill
- Tool integration
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
DOM components provide a bridge for running web-specific code within Expo applications. By using the 'use dom' directive, developers can render React web components inside a native webview on iOS and Android while maintaining standard web behavior on browser platforms. This approach allows for the seamless integration of web-only libraries, complex CSS layouts, and existing web codebases into native mobile environments without requiring a full rewrite.
What it does
- Enables running web-only React libraries like Recharts or syntax highlighters in native apps.
- Allows incremental migration of existing web codebases to native platforms.
- Provides a mechanism to bridge native functions to web components via async props.
- Supports complex HTML/CSS layouts that are difficult to replicate with standard React Native components.
- Handles platform-specific rendering by using webviews on mobile and native DOM on web.
- Facilitates embedding iframes or external web content within a native screen.
When to use it
- When you need to use a web-only library that depends on browser DOM APIs.
- When migrating complex web UI components to a mobile app incrementally.
- When you need to render complex HTML or CSS layouts not supported by React Native.
- When you need to embed external web content or iframes.
- When you need to utilize WebGL or Canvas graphics in your application.
Advantages
- Reduces development time by reusing existing web components.
- Enables access to the vast ecosystem of React web libraries.
- Maintains a consistent experience across web and mobile platforms.
- Allows for isolated styling and logic within the webview context.
- Provides a clear path for bridging native functionality to web-based components.
Usage examples
- “How do I use a web-only chart library in my Expo app?”
- Create a file with the 'use dom' directive, import your chart library, and pass the data as props from your native screen to the DOM component.
- “Can I trigger a native alert from a web component?”
- Yes, define an async function in your native parent component and pass it as a prop to the DOM component, which can then call it to trigger native UI.
- “How do I handle routing inside a DOM component?”
- You can use the expo-router Link component directly, but for hooks like usePathname, you must read the values in the native parent and pass them down as props.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/openai/use-dom/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip use-dom.zip -d .agents/skills
Documentation by Expo Team
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 —
_layoutfiles 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
Must have
'use dom';directive at the top of the fileSingle default export — One React component per file
Own file — Cannot be defined inline or combined with native components
Serializable props only — Strings, numbers, booleans, arrays, plain objects
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
Files
- ATTRIBUTION.mdAdded by AutoGineer804 B
- LICENSELicence file added by AutoGineer1.1 KiB
- SKILL.md9.5 KiB
- agents/openai.yaml358 B
ATTRIBUTION.md
Attribution
This folder is an unmodified copy of the Agent Skill "use-dom", distributed by AutoGineer (https://autogineer.com/agent-skills).
Commit: d420d13051bffe4543ed39e30fa46b87148e47dd
Created by: Expo Team (https://expo.dev)
Publisher: OpenAI (https://github.com/openai)
Licence: MIT License (https://spdx.org/licenses/MIT.html)
Licence evidence: declared in the frontmatter ("license: MIT")
Retrieved: 2026-11-07
The skill's own files are unmodified copies. AutoGineer is not affiliated with or endorsed by the author or the publisher.
Files added by AutoGineer
ATTRIBUTION.md: this file, written by AutoGineer
LICENSE: copied from plugins/expo/LICENSE
Requirements and compatibility
Written for: OpenAI Codex
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Expo Team
- Publisher
- OpenAI (Official)
- Distributed by
- OpenAI
- Source
- openai/plugins · plugins/expo/skills/use-dom · commit d420d13
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from plugins/expo/LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Expo Team or OpenAI.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download use-dom.zip