--- name: react-strict-dom description: Set up react-strict-dom with Babel, PostCSS, and CSS-wrapped HTML components for universal Expo apps --- # React Strict DOM Setup for Expo This guide covers setting up react-strict-dom in an Expo project with Tailwind CSS v4 and react-native-css for universal app development. ## Overview react-strict-dom provides a subset of HTML/CSS that works identically across web and native platforms. This setup includes: - **Babel preset** - Transforms react-strict-dom components for each platform - **PostCSS plugin** - Processes styles for web builds - **CSS directive** - Enables react-strict-dom styles in global CSS - **HTML wrapper components** - CSS-wrapped primitives with Tailwind className support ## Installation Install react-strict-dom using bun: ```bash bun install react-strict-dom@0.0.54 ``` > Note: Version 0.0.55 has a broken dependency (`postcss-react-strict-dom@0.0.55` doesn't exist). Use 0.0.54 until this is resolved. ## Babel Configuration Create or update `babel.config.js` in the project root: ```javascript const reactStrictPreset = require("react-strict-dom/babel-preset"); function getPlatform(caller) { return caller && caller.platform; } function getIsDev(caller) { if (caller?.isDev != null) return caller.isDev; // https://babeljs.io/docs/options#envname return ( process.env.BABEL_ENV === "development" || process.env.NODE_ENV === "development" ); } module.exports = function (api) { //api.cache(true); const platform = api.caller(getPlatform); const dev = api.caller(getIsDev); return { presets: [ "babel-preset-expo", [reactStrictPreset, { debug: true, dev, platform }], ], }; }; ``` ### Configuration Options - `debug: true` - Enables debug output during development - `dev` - Automatically detected from environment - `platform` - Passed by Metro/bundler for platform-specific transforms ## PostCSS Configuration Update `postcss.config.mjs` to include the react-strict-dom plugin: ```javascript export default { plugins: { "@tailwindcss/postcss": {}, "react-strict-dom/postcss-plugin": { include: ["src/**/*.{js,jsx,mjs,ts,tsx}"], }, }, }; ``` The `include` option specifies which files to process for react-strict-dom styles. ## Global CSS Directive Add the `@react-strict-dom` directive at the top of your global CSS file (e.g., `src/global.css`): ```css @react-strict-dom; @import "tailwindcss/theme.css" layer(theme); @import "tailwindcss/preflight.css" layer(base); @import "tailwindcss/utilities.css"; /* ... rest of your styles */ ``` This directive must be the first rule in the file for react-strict-dom styles to work correctly. ## HTML Components with Tailwind Support Create CSS-wrapped HTML components that support Tailwind className at `src/html/index.tsx`: ```typescript import { useCssElement } from "react-native-css"; import React from "react"; import { html as rsd } from "react-strict-dom"; import { StyleSheet } from "react-native"; function createCssComponent( elementName: T, displayName?: string ) { type Props = React.ComponentProps<(typeof rsd)[T]> & { className?: string; }; let Component: (props: Props) => React.ReactElement; if (process.env.EXPO_OS === "web") { Component = (props: Props) => { // eslint-disable-next-line import/namespace return useCssElement(rsd[elementName], props, { // @ts-expect-error className: "style", }); }; } else { Component = ({ style, ...props }: Props) => { const normal = props as any; if (style) { normal.style = normalizeStyle(style); } return useCssElement( // eslint-disable-next-line import/namespace rsd[elementName], normal, { // @ts-expect-error className: "style", } ); }; } (Component as any).displayName = displayName || `CSS(${elementName})`; return Component; } function normalizeStyle(style: any) { if (!style) return style; const flat = StyleSheet.flatten(style); if (flat.backgroundImage) { flat.experimental_backgroundImage = flat.backgroundImage; delete flat.backgroundImage; } return flat; } export const html = { button: createCssComponent("button"), div: createCssComponent("div"), h1: createCssComponent("h1"), h2: createCssComponent("h2"), h3: createCssComponent("h3"), h4: createCssComponent("h4"), h5: createCssComponent("h5"), h6: createCssComponent("h6"), p: createCssComponent("p"), span: createCssComponent("span"), img: createCssComponent("img"), input: createCssComponent("input"), textarea: createCssComponent("textarea"), a: createCssComponent("a"), ul: createCssComponent("ul"), ol: createCssComponent("ol"), li: createCssComponent("li"), nav: createCssComponent("nav"), header: createCssComponent("header"), footer: createCssComponent("footer"), main: createCssComponent("main"), section: createCssComponent("section"), article: createCssComponent("article"), aside: createCssComponent("aside"), label: createCssComponent("label"), form: createCssComponent("form"), i: createCssComponent("i"), b: createCssComponent("b"), strong: createCssComponent("strong"), em: createCssComponent("em"), code: createCssComponent("code"), pre: createCssComponent("pre"), blockquote: createCssComponent("blockquote"), hr: createCssComponent("hr"), }; ``` ### Usage Import and use the HTML components with Tailwind classes: ```tsx import { html } from "@/html"; export function MyComponent() { return ( Hello World This works on both web and native! Click me ); } ``` ## How It Works ### Web On web, react-strict-dom renders actual HTML elements (`
`, `