Unistyles 3
Set up Unistyles 3 for UI in an Expo app with ReadyNative: C++ StyleSheet with themes/breakpoints behind @/ui · dev build required (no Expo Go)
Expo Go: no - dev build required
Ships native code. Make a dev build with bunx expo run:ios / bunx expo run:android (or bunx eas-cli build --profile development).
A C++/Nitro style engine: StyleSheet.create((theme) => …) styles are bound to the shadow tree and re-themed without a React re-render, as the kit in src/components/ui/ (src/ui/unistyles/ before setup). Pick it when theme switching and breakpoints need to be as cheap as possible and you're already on a dev build - it's the one UI option that can't run in Expo Go.
Setup
bun run setup --ui unistyles- Make a dev build:
npx expo run:ios/npx expo run:android(oreas build --profile development). Unistyles is a native module. - Set your colours in
src/theme/tokens.ts-src/unistyles.tsbuilds both themes from them, no generate step. bun run start -- -c- once, since the Babel plugin and the entry file both changed.
Setup installs react-native-unistyles ^3.3.0, react-native-nitro-modules 0.37.1 (exact - Unistyles pins the Nitro ABI) and react-native-edge-to-edge ^1.8.1 with its config plugin, and writes:
babel.config.js-babel-preset-expoplus["react-native-unistyles/plugin", { root: "src" }].index.tsat the root pluspackage.jsonmain: "index.ts", sosrc/unistyles.tsrunsStyleSheet.configurebeforeexpo-router/entrycreates any stylesheet. Switching stacks restoresmain: "expo-router/entry".src/unistyles.ts- thelight/darkthemes, breakpointsxssmmdlgxl,settings.adaptiveThemes: true, and theUnistylesThemes/UnistylesBreakpointsaugmentations.src/app/+html.tsx(web shell), every primitive undersrc/components/ui/, andsrc/components/ui/jest-setup.ts.
Usage
Screens use the primitives:
import { Box, Screen, Text } from "@/components/ui";
<Screen>
<Box p={4} gap={2}>
<Text variant="title">Library</Text>
</Box>
</Screen>;Your own components write theme functions:
import { StyleSheet } from "react-native-unistyles";
const styles = StyleSheet.create((theme) => ({
card: { backgroundColor: theme.colors.card, padding: theme.space[4] },
}));variants plus styles.useVariants({ tone: "danger" }) for prop-driven looks; useTheme() for inline token colours. Theme mode is the usual setThemeMode("system" | "light" | "dark") from @/components/ui - ThemeProvider mirrors it into the runtime (system turns adaptive themes on, an explicit mode turns them off, then setTheme(mode)).
To add a primitive to the kit: create src/components/ui/<name>.tsx, add its prop type to src/components/ui/types.ts, and export it from src/components/ui/index.ts.
Gotchas
- No Expo Go.
bun run doctorflags it in itsdevBuildcheck.expo exportstill works - it's only the JS bundle. setThemethrows while adaptive themes are on, which is whyThemeProvidercallssetAdaptiveThemes(false)first.- The official jest mock strips
variantsand resolves against the first registered theme (light), so tests see base styles only. react-native-edge-to-edgeis an optional peer of Unistyles ≥ 3.1 (Expo 54+ is edge-to-edge itself), installed for ecosystem compatibility.expo installcan't add its plugin to a dynamicapp.config.ts, so the module declares it inapp.expo.plugins.- Native is the target; the web export is best-effort.
Remove it
Every app needs a ui option, so there is no none to fall back to - you switch instead. While modules/ exists (a tree set up with --keep-modules), one call does it:
bun run setup --ui nativewind4 --yes --keep-modulesA finalized tree can't switch: clone a fresh copy of your tier repo, run setup there with the new option and port your code across - see Can I change a module after setup?.
Reference
Everything below is generated from modules/ui/unistyles/module.json - the same file bun run setup reads, so it is what actually lands in your repo.
Install
bun run setup --ui unistylesModule id: ui/unistyles.
Dependencies
| Package | Version | Kind |
|---|---|---|
react-native-edge-to-edge | ^1.8.1 | dependency |
react-native-nitro-modules | 0.37.1 | dependency |
react-native-unistyles | ^3.3.0 | dependency |
Config plugins
Merged into app.config.ts through .readynative.json (modules.app):
react-native-edge-to-edge
Providers
Rendered in src/providers.tsx (lower order = outermost):
| Order | Provider | From |
|---|---|---|
| 50 | ThemeProvider as UiThemeProvider | @/ui |
Doctor checks
- dev build toolchain (Xcode / Android SDK)
After setup
- Unistyles is a native (Nitro) module: make a dev build with
npx expo run:ios/npx expo run:android(oreas build --profile development). Expo Go will not load it. - Run
bun run start -- -conce so Metro picks up the Unistyles Babel plugin and the new entry file.
Files
26 files copied to the project root
babel.config.jsindex.tsmetro.config.jssrc/app/+html.tsxsrc/ui/unistyles/box.tsxsrc/ui/unistyles/button.tsxsrc/ui/unistyles/card.tsxsrc/ui/unistyles/divider.tsxsrc/ui/unistyles/empty-state.tsxsrc/ui/unistyles/error-state.tsxsrc/ui/unistyles/icon.tsxsrc/ui/unistyles/index.tssrc/ui/unistyles/input.tsxsrc/ui/unistyles/jest-setup.tssrc/ui/unistyles/list.tsxsrc/ui/unistyles/loading.tsxsrc/ui/unistyles/pressable.tsxsrc/ui/unistyles/row.tsxsrc/ui/unistyles/screen.tsxsrc/ui/unistyles/sheet.tsxsrc/ui/unistyles/skeleton.tsxsrc/ui/unistyles/switch.tsxsrc/ui/unistyles/text.tsxsrc/ui/unistyles/theme-provider.tsxsrc/ui/unistyles/toast.tsxsrc/unistyles.ts