A visual-regression studio for Expo / React Native UI components. Not a component library.
The components here are real React Native — StyleSheet and Pressable, no HTML/CSS. Web components plus Storybook is a solved problem with plenty of open-source examples; wiring native components into generated stories and CI visual baselines is not, and that wiring is what this repo is. Every component ships a deterministic example file that generates its Storybook story and its Chromatic visual baseline. One component tree renders on iOS, Android, and Web; visual regression runs on the web render.
▶ Browse the live Storybook — every native component and story, rendered via react-native-web and published from CI. No install needed.
The name, decoded: shad = shadcn-style (you own the component source), expo = the platform, studio = the authoring and visual-regression harness around it.
Three depths, from zero-install browsing to the components running natively. Stop wherever you have what you came for.
1. Look — zero install. Browse the hosted Storybook. Everything in it is a native component seen through its react-native-web render.
2. Run it locally — no Xcode, no emulator, no signing:
pnpm install
pnpm preview # the same Storybook at http://localhost:60063. Run it natively — the claim the whole repo rests on:
pnpm ios # or: pnpm androidThe same component tree boots in the simulator, on-device Storybook included (the /storybook route). Simulator/emulator setup lives in Full native development below. No simulator handy? pnpm web runs the app shell in your browser instead.
You're building a real Expo app, probably with AI coding agents in the loop. You own your component layer and you want proof — a story and a visual baseline per component — that a change didn't silently break your UI on the way to production.
Who it's not for:
- If you want a drop-in component library with dozens of ready components, use react-native-reusables. It's excellent and that's its job.
- If you're a large design-system org that needs breadth, theming infrastructure, and support — this is a solo-maintained reference setup, not that.
react-native-reusables, gluestack-ui, and Tamagui give you components. This repo gives components a workflow: a deterministic example, a generated Storybook story, a Chromatic baseline, and CI that runs typecheck/lint/test on every PR. They're complementary — you can bring their components into this studio and put them under the same visual-regression harness.
The examples files serve double duty: each *.examples.tsx is at once a Storybook story source and a machine-readable usage example a coding agent can consume. shadcn's ecosystem moved in this direction on the web in 2026 (MCP, llms.txt); on React Native this workflow is still rare.
packages/ui/src/native/common/button.tsx ← the component (plain StyleSheet, no styling deps)
packages/ui/src/native/common/button.examples.tsx ← deterministic examples: storyMeta + storyExamples
│
│ pre-commit hook (or: pnpm --filter expo-app stories:generate)
▼
apps/expo/.rnstorybook/stories/auto/common/button.examples.stories.tsx ← generated, do not edit
│
├── web Storybook (Vite + React Native Web) → Chromatic visual baseline
└── on-device Storybook (/storybook route in the Expo app)
- Write a presentational component in
packages/ui/src/native/— no hooks into navigation, network, or app state. - Add a
*.examples.tsxnext to it exportingstoryMeta+storyExampleswith deterministic data. Copy an existing one (e.g.button.examples.tsx) as the template. - Commit. The pre-commit hook regenerates stories and formats; CI typechecks, lints, and tests; Chromatic diffs the web Storybook against the locked baselines.
- The app shell (
apps/expo) imports the same components, so what Storybook verifies is what ships.
The inventory is deliberately small. Every entry is fully wired — example, story, baseline — because adding a component here means adding all three, and the tooling generates two of them for you.
| Component | Source | Example | Story | Visual baseline |
|---|---|---|---|---|
| Badge | common/badge.tsx |
✓ | ✓ | ✓ |
| Button | common/button.tsx |
✓ | ✓ | ✓ |
| Card | common/card.tsx |
✓ | ✓ | ✓ |
| TextField | common/text-field.tsx |
✓ | ✓ | ✓ |
| ToggleRow | common/toggle-row.tsx |
✓ | ✓ | ✓ |
| Tasks screen | screen/tasks.tsx |
✓ | ✓ | ✓ |
Components use plain React Native StyleSheet — no NativeWind, no Tailwind config to wire up before they render.
The components are distributed as source through a shadcn-compatible registry — you copy the code and own it, nothing to npm install. From your Expo project:
npx shadcn@latest add https://raw.githubusercontent.com/Ge-limin/shad-expo-studio/main/public/r/button.jsonAvailable items: badge, button, card, text-field, toggle-row, and tasks-screen (which pulls its four component dependencies automatically). Files land under components/studio/ in your project.
One caveat: the shadcn CLI doesn't detect Expo as a framework, so it needs a components.json in your project root. If you don't have one, create this minimal version first:
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": { "config": "", "css": "", "baseColor": "neutral", "cssVariables": false },
"aliases": { "components": "@/components", "ui": "@/components/ui", "lib": "@/lib", "utils": "@/lib/utils", "hooks": "@/hooks" }
}The tailwind/aliases fields are required by the schema but unused here — every registry item declares an explicit target path and has zero styling dependencies.
CI rebuilds the registry on every PR and fails if public/r/ is out of sync with the component sources, so what you install always matches what Storybook and Chromatic verified.
pnpm preview # web Storybook at :6006 (regenerates stories first)
pnpm web # run the app shell in the browser (includes the /storybook route)
pnpm lint && pnpm typecheck # eslint + typescript checks
pnpm test # Jest (set WATCHMAN_DISABLE=1 if watchman is unavailable)
pnpm format:fix # Prettier across packages
pnpm chromatic # build web Storybook and upload snapshots to ChromaticHusky hooks do the routine work: pre-commit regenerates stories + formats, pre-push runs typecheck + lint.
The app shell always exposes the on-device /storybook route (and an Open Storybook button on the home and Tasks screens) — the studio is the product. Set EXPO_PUBLIC_STORYBOOK_ENABLED=false to hide it, e.g. when reusing the shell as an app template.
CI publishes the web Storybook to Chromatic for visual diffs on in-repo PRs. Keep examples deterministic — no random data, no dates from the clock — so snapshots are stable.
To run it yourself:
- Put
CHROMATIC_PROJECT_TOKEN=...inapps/expo/.env.local(gitignored). pnpm chromaticfrom the repo root. It builds the React Native Web Storybook and uploads snapshots.
iOS / Android environment setup for `pnpm ios` / `pnpm android`
- Follow the Expo environment setup for iOS simulator and Android emulator, choosing development build without EAS. Don't run the local commands on that page.
- For iOS signing, follow setup-xcode-signing.
- Then:
cd apps/expo
pnpm stories:generate # regenerate story files
npx expo install expo-dev-client # native dev client deps
pnpm android # build + launch Android dev client
pnpm ios # build + launch iOS dev clientThe repo pins Node ≥ 20.10 and pnpm 10.17.1 (packageManager field — corepack picks it up automatically; pnpm install also installs the git hooks).
apps/expo/ Expo Router app shell: routes, state, data, navigation. Storybook config + generated stories.
packages/ui/ Presentational components only + their *.examples.tsx. No router/network/app state.
tooling/ Shared eslint / prettier / tsconfig.
The split is the point: the UI package stays pure and deterministic so stories and baselines stay reliable, while everything stateful lives in route wrappers under apps/expo/app/.
The contribution unit is a component plus its examples file — see CONTRIBUTING.md for the full authoring loop.
Code volume exploded in the AI-assisted era and manual review misses subtle visual regressions. Deterministic stories plus Chromatic baselines catch the drift that slips past PR review. Separating presentational UI from product code lets a designer, or an AI agent, own the UI layer while app code stays focused on data and flows. And cross-platform Storybook coverage for React Native is still painful to wire up from scratch — this repo is that wiring, done once, as a working reference.
