{"id":85005,"plugin_id":"plugin_asdk_app_6aec576a7d8c8191ab1092de040af13a","kind":"skill","collection_source":"plugin_package","comparison_source":null,"observed_at":"2026-11-06T18:22:11.146Z","digest":"52e4db87266a3993011b13e43b44e34aaf5c19f1e00c818d4166e9ff7fb9f221","against":null,"payload":{"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. 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}],"name":"expo-dom","skill_md_contents":"---\nname: expo-dom\ndescription: 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.\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\nWhen the user requests feedback submission, use:\n```bash\nnpx --yes @expo/agent-cli@1.0.23 feedback --category skills --subject \"expo-dom\" \"<actionable feedback>\"\n```\nInclude only relevant technical details; keep credentials, private data, and conversation contents out of feedback.\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}