← ExpoCONTENT HISTORY

Update to Expo

Snapshot Sep 30, 2026 · 23:11 UTC · version 1.13.7

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "name": "expo-dom",
  "description": "Framework (OSS). Use Expo DOM components to run web code in a webview on native and as-is on web. Migrate web code to native incrementally. For the end-to-end migration of a whole web app, use the expo-web-to-native skill.",
  "included_files": [
    {
      "relative_path": "agents/openai.yaml",
      "size_in_bytes": 371
    }
  ],
  "skill_md_contents": "---\nname: expo-dom\ndescription: Framework (OSS). Use Expo DOM components to run web code in a webview on native and as-is on web. Migrate web code to native incrementally. For the end-to-end migration of a whole web app, use the expo-web-to-native skill.\nversion: 1.0.0\nlicense: MIT\n---\n\n## What are DOM Components?\n\nDOM 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.\n\n## When to Use DOM Components\n\nUse DOM components when you need:\n\n- **Web-only libraries** — Charts (recharts, chart.js), syntax highlighters, rich text editors, or any library that depends on DOM APIs\n- **Migrating web code** — Bring existing React web components to native without rewriting\n- **Complex HTML/CSS layouts** — When CSS features aren't available in React Native\n- **iframes or embeds** — Embedding external content that requires a browser context\n- **Canvas or WebGL** — Web graphics APIs not available natively\n\n## When NOT to Use DOM Components\n\nAvoid DOM components when:\n\n- **Native performance is critical** — Webviews add overhead\n- **Simple UI** — React Native components are more efficient for basic layouts\n- **Deep native integration** — Use local modules instead for native APIs\n- **Layout routes** — `_layout` files cannot be DOM components\n\n## Basic DOM Component\n\nCreate a new file with the `'use dom';` directive at the top:\n\n```tsx\n// components/WebChart.tsx\n\"use dom\";\n\nexport default function WebChart({\n  data,\n}: {\n  data: number[];\n  dom: import(\"expo/dom\").DOMProps;\n}) {\n  return (\n    <div style={{ padding: 20 }}>\n      <h2>Chart Data</h2>\n      <ul>\n        {data.map((value, i) => (\n          <li key={i}>{value}</li>\n        ))}\n      </ul>\n    </div>\n  );\n}\n```\n\n## Rules for DOM Components\n\n1. **Must have `'use dom';` directive** at the top of the file\n2. **Single default export** — One React component per file\n3. **Own file** — Cannot be defined inline or combined with native components\n4. **Serializable props only** — Strings, numbers, booleans, arrays, plain objects\n5. **Include CSS in the component file** — DOM components run in isolated context\n\n## The `dom` Prop\n\nEvery DOM component receives a special `dom` prop for webview configuration. Always type it in your props:\n\n```tsx\n\"use dom\";\n\ninterface Props {\n  content: string;\n  dom: import(\"expo/dom\").DOMProps;\n}\n\nexport default function MyComponent({ content }: Props) {\n  return <div>{content}</div>;\n}\n```\n\n### Common `dom` Prop Options\n\n```tsx\n// Disable body scrolling\n<DOMComponent dom={{ scrollEnabled: false }} />\n\n// Flow under the notch (disable safe area insets)\n<DOMComponent dom={{ contentInsetAdjustmentBehavior: \"never\" }} />\n\n// Control size manually\n<DOMComponent dom={{ style: { width: 300, height: 400 } }} />\n\n// Combine options\n<DOMComponent\n  dom={{\n    scrollEnabled: false,\n    contentInsetAdjustmentBehavior: \"never\",\n    style: { width: '100%', height: 500 }\n  }}\n/>\n```\n\n## Exposing Native Actions to the Webview\n\nPass async functions as props to expose native functionality to the DOM component:\n\n```tsx\n// app/index.tsx (native)\nimport { Alert } from \"react-native\";\nimport DOMComponent from \"@/components/dom-component\";\n\nexport default function Screen() {\n  return (\n    <DOMComponent\n      showAlert={async (message: string) => {\n        Alert.alert(\"From Web\", message);\n      }}\n      saveData={async (data: { name: string; value: number }) => {\n        // Save to native storage, database, etc.\n        console.log(\"Saving:\", data);\n        return { success: true };\n      }}\n    />\n  );\n}\n```\n\n```tsx\n// components/dom-component.tsx\n\"use dom\";\n\ninterface Props {\n  showAlert: (message: string) => Promise<void>;\n  saveData: (data: {\n    name: string;\n    value: number;\n  }) => Promise<{ success: boolean }>;\n  dom?: import(\"expo/dom\").DOMProps;\n}\n\nexport default function DOMComponent({ showAlert, saveData }: Props) {\n  const handleClick = async () => {\n    await showAlert(\"Hello from the webview!\");\n    const result = await saveData({ name: \"test\", value: 42 });\n    console.log(\"Save result:\", result);\n  };\n\n  return <button onClick={handleClick}>Trigger Native Action</button>;\n}\n```\n\n## Using Web Libraries\n\nDOM components can use any web library:\n\n```tsx\n// components/syntax-highlight.tsx\n\"use dom\";\n\nimport SyntaxHighlighter from \"react-syntax-highlighter\";\nimport { docco } from \"react-syntax-highlighter/dist/esm/styles/hljs\";\n\ninterface Props {\n  code: string;\n  language: string;\n  dom?: import(\"expo/dom\").DOMProps;\n}\n\nexport default function SyntaxHighlight({ code, language }: Props) {\n  return (\n    <SyntaxHighlighter language={language} style={docco}>\n      {code}\n    </SyntaxHighlighter>\n  );\n}\n```\n\n```tsx\n// components/chart.tsx\n\"use dom\";\n\nimport {\n  LineChart,\n  Line,\n  XAxis,\n  YAxis,\n  CartesianGrid,\n  Tooltip,\n} from \"recharts\";\n\ninterface Props {\n  data: Array<{ name: string; value: number }>;\n  dom: import(\"expo/dom\").DOMProps;\n}\n\nexport default function Chart({ data }: Props) {\n  return (\n    <LineChart width={400} height={300} data={data}>\n      <CartesianGrid strokeDasharray=\"3 3\" />\n      <XAxis dataKey=\"name\" />\n      <YAxis />\n      <Tooltip />\n      <Line type=\"monotone\" dataKey=\"value\" stroke=\"#8884d8\" />\n    </LineChart>\n  );\n}\n```\n\n## CSS in DOM Components\n\nCSS imports must be in the DOM component file since they run in isolated context:\n\n```tsx\n// components/styled-component.tsx\n\"use dom\";\n\nimport \"@/styles.css\"; // CSS file in same directory\n\nexport default function StyledComponent({\n  dom,\n}: {\n  dom: import(\"expo/dom\").DOMProps;\n}) {\n  return (\n    <div className=\"container\">\n      <h1 className=\"title\">Styled Content</h1>\n    </div>\n  );\n}\n```\n\nOr use inline styles / CSS-in-JS:\n\n```tsx\n\"use dom\";\n\nconst styles = {\n  container: {\n    padding: 20,\n    backgroundColor: \"#f0f0f0\",\n  },\n  title: {\n    fontSize: 24,\n    color: \"#333\",\n  },\n};\n\nexport default function StyledComponent({\n  dom,\n}: {\n  dom: import(\"expo/dom\").DOMProps;\n}) {\n  return (\n    <div style={styles.container}>\n      <h1 style={styles.title}>Styled Content</h1>\n    </div>\n  );\n}\n```\n\n## Expo Router in DOM Components\n\nThe expo-router `<Link />` component and router API work inside DOM components:\n\n```tsx\n\"use dom\";\n\nimport { Link, useRouter } from \"expo-router\";\n\nexport default function Navigation({\n  dom,\n}: {\n  dom: import(\"expo/dom\").DOMProps;\n}) {\n  const router = useRouter();\n\n  return (\n    <nav>\n      <Link href=\"/about\">About</Link>\n      <button onClick={() => router.push(\"/settings\")}>Settings</button>\n    </nav>\n  );\n}\n```\n\n### Router APIs That Require Props\n\nThese hooks don't work directly in DOM components because they need synchronous access to native routing state:\n\n- `useLocalSearchParams()`\n- `useGlobalSearchParams()`\n- `usePathname()`\n- `useSegments()`\n- `useRootNavigation()`\n- `useRootNavigationState()`\n\n**Solution:** Read these values in the native parent and pass as props:\n\n```tsx\n// app/[id].tsx (native)\nimport { useLocalSearchParams, usePathname } from \"expo-router\";\nimport DOMComponent from \"@/components/dom-component\";\n\nexport default function Screen() {\n  const { id } = useLocalSearchParams();\n  const pathname = usePathname();\n\n  return <DOMComponent id={id as string} pathname={pathname} />;\n}\n```\n\n```tsx\n// components/dom-component.tsx\n\"use dom\";\n\ninterface Props {\n  id: string;\n  pathname: string;\n  dom?: import(\"expo/dom\").DOMProps;\n}\n\nexport default function DOMComponent({ id, pathname }: Props) {\n  return (\n    <div>\n      <p>Current ID: {id}</p>\n      <p>Current Path: {pathname}</p>\n    </div>\n  );\n}\n```\n\n## Detecting DOM Environment\n\nCheck if code is running in a DOM component:\n\n```tsx\n\"use dom\";\n\nimport { IS_DOM } from \"expo/dom\";\n\nexport default function Component({\n  dom,\n}: {\n  dom?: import(\"expo/dom\").DOMProps;\n}) {\n  return <div>{IS_DOM ? \"Running in DOM component\" : \"Running natively\"}</div>;\n}\n```\n\n## Assets\n\nPrefer requiring assets instead of using the public directory:\n\n```tsx\n\"use dom\";\n\n// Good - bundled with the component\nconst logo = require(\"../assets/logo.png\");\n\nexport default function Component({\n  dom,\n}: {\n  dom: import(\"expo/dom\").DOMProps;\n}) {\n  return <img src={logo} alt=\"Logo\" />;\n}\n```\n\n## Usage from Native Components\n\nImport and use DOM components like regular components:\n\n```tsx\n// app/index.tsx\nimport { View, Text } from \"react-native\";\nimport WebChart from \"@/components/web-chart\";\nimport CodeBlock from \"@/components/code-block\";\n\nexport default function HomeScreen() {\n  return (\n    <View style={{ flex: 1 }}>\n      <Text>Native content above</Text>\n\n      <WebChart data={[10, 20, 30, 40, 50]} dom={{ style: { height: 300 } }} />\n\n      <CodeBlock\n        code=\"const x = 1;\"\n        language=\"javascript\"\n        dom={{ scrollEnabled: true }}\n      />\n\n      <Text>Native content below</Text>\n    </View>\n  );\n}\n```\n\n## Platform Behavior\n\n| Platform | Behavior                            |\n| -------- | ----------------------------------- |\n| iOS      | Rendered in WKWebView               |\n| Android  | Rendered in WebView                 |\n| Web      | Rendered as-is (no webview wrapper) |\n\nOn web, the `dom` prop is ignored since no webview is needed.\n\n## Tips\n\n- DOM components hot reload during development\n- Keep DOM components focused — don't put entire screens in webviews\n- Use native components for navigation chrome, DOM components for specialized content\n- Test on all platforms — web rendering may differ slightly from native webviews\n- Large DOM components may impact performance — profile if needed\n- The webview has its own JavaScript context — cannot directly share state with native\n\n## Submitting Feedback\nIf you encounter errors, misleading or outdated information in this skill, report it so Expo can improve:\n```bash\nnpx --yes submit-expo-feedback@latest --category skills --subject \"expo-dom\" \"<actionable feedback>\"\n```\nOnly submit when you have something specific and actionable to report. Include as much relevant context as possible.\nIf an AI agent repeatedly failed or the user had to take over an Expo task, load the expo-skill-feedback skill and follow its eval-candidate flow instead of reusing the command above.\n"
}

SHA-256: ea3fa914948cfeb46b8b5893a4cee4ecafad4de7d1131582dc5ae85f8e03decd