NativeWind 4
Set up NativeWind 4 for UI in an Expo app with ReadyNative: Stable · Tailwind 3 classes + RNR components behind @/ui · Expo Go OK
Expo Go: yes
Runs in Expo Go; no dev build needed for this module.
The default UI stack: stable NativeWind 4.2 (nativewind@4.2.7) on Tailwind 3, with React Native Reusables (RNR) text / button as the kit in src/components/ui/ (src/ui/nativewind4/ before setup). Every preset and the committed tree use it. Pick nativewind5 (RC · Tailwind 4) instead only if you want Tailwind 4 before NativeWind 5 is released. Pull components straight from the RNR registry - components.json already points at @/components/ui. Expo Go works.
Setup
bun run setup --ui nativewind4- Set your colours in
src/theme/tokens.ts(hex). bun run gen:theme- converts them to HSL triples and rewritessrc/global.cssandtailwind.config.js.bun run start -- -c- once, so Metro picks up the NativeWind transform.
Setup writes babel.config.js (jsxImportSource: "nativewind" + nativewind/babel), metro.config.js (withNativeWind, inlineRem: 16), tailwind.config.js (darkMode: "class", colours as hsl(var(--x) / <alpha-value>)), src/global.css, nativewind-env.d.ts and components.json, and registers ThemeProvider (order 50).
8-digit hex loses its alpha
HSL triples can't carry alpha, so a token like the dark border #ffffff1a is composited onto that mode's background by the generator. The generated CSS says so inline.
Usage
Screens use the primitives:
import { Box, Button, Screen, Text } from "@/components/ui";
<Screen>
<Box p={4} gap={2}>
<Text variant="heading">Settings</Text>
<Button onPress={signOut}>Sign out</Button>
</Box>
</Screen>;Your own components use className for static classes and useTheme() for anything token-driven:
import { useTheme } from "@/components/ui";
const { colors, mode } = useTheme(); // resolved-mode hex, for inline stylesTo add a primitive to the kit: create it in src/components/ui/, add its prop type to src/components/ui/types.ts, and export it from src/components/ui/index.ts.
Gotchas
darkMode: "class"means NativeWind never follows the OS by itself.ThemeProvidercallscolorScheme.set(mode)with the stored preference -Appearance.setColorSchemeon native, thedarkclass on<html>on web.ForcedMode(the dev catalog) wraps a subtree in aViewwithstyle={vars(hslTriples)}because NativeWind 4 has noVariableContextProvider. Inside it,dark:utilities still follow the global scheme, and it adds one layout node.- nativewind #1834 (open): LogBox breaks on RN 0.86 with
nativewind/babelin the preset list (fix PR #1840 unmerged). Dev-only; release bundles andexpo exportare fine. If the red box misbehaves, drop"nativewind/babel"frombabel.config.js- onlyReact.createElement-style code losesclassName. - Web is best-effort:
colorScheme.setthrows outside a browser (guarded) and SSR export of.dark:rootis untested. - Before setup, or with
--keep-modules,bun run setup --ui <other>swaps the adapter and config. Screens don't change.
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 nativewind5 --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/nativewind4/module.json - the same file bun run setup reads, so it is what actually lands in your repo.
Install
bun run setup --ui nativewind4Module id: ui/nativewind4 (the default for this category).
Dependencies
| Package | Version | Kind |
|---|---|---|
@rn-primitives/slot | ^1.5.2 | dependency |
class-variance-authority | ^0.7.1 | dependency |
clsx | ^2.1.1 | dependency |
nativewind | 4.2.7 | dependency |
tailwind-merge | ^3.7.0 | dependency |
tailwindcss-animate | ^1.0.7 | dependency |
tailwindcss | ^3.4.19 | devDependency |
Providers
Rendered in src/providers.tsx (lower order = outermost):
| Order | Provider | From |
|---|---|---|
| 50 | ThemeProvider as UiThemeProvider | @/ui |
After setup
- Run
bun run start -- -conce so Metro picks up the NativeWind transform.
Files
31 files copied to the project root
babel.config.jsmetro.config.jstailwind.config.jscomponents.jsonnativewind-env.d.tssrc/global.csssrc/ui/nativewind4/box.tsxsrc/ui/nativewind4/button.tsxsrc/ui/nativewind4/card.tsxsrc/ui/nativewind4/cn.tssrc/ui/nativewind4/divider.tsxsrc/ui/nativewind4/empty-state.tsxsrc/ui/nativewind4/error-state.tsxsrc/ui/nativewind4/hsl.tssrc/ui/nativewind4/icon.tsxsrc/ui/nativewind4/index.tssrc/ui/nativewind4/jest-setup.tssrc/ui/nativewind4/input.tsxsrc/ui/nativewind4/list.tsxsrc/ui/nativewind4/loading.tsxsrc/ui/nativewind4/pressable.tsxsrc/ui/nativewind4/rnr-button.tsxsrc/ui/nativewind4/rnr-text.tsxsrc/ui/nativewind4/row.tsxsrc/ui/nativewind4/screen.tsxsrc/ui/nativewind4/sheet.tsxsrc/ui/nativewind4/skeleton.tsxsrc/ui/nativewind4/switch.tsxsrc/ui/nativewind4/text.tsxsrc/ui/nativewind4/theme-provider.tsxsrc/ui/nativewind4/toast.tsx