NativeWind v4 — Expo Installation
Purpose
Walk through every required step to add NativeWind v4 to an existing Expo project, producing a working Tailwind CSS setup for iOS, Android, and web.
When To Use
- "add NativeWind to my Expo project"
- "set up Tailwind CSS in Expo / React Native"
- "configure nativewind v4"
- "install nativewind with Expo"
Inputs
- An existing Expo project (SDK 49+)
- Package manager preference (npm / yarn / pnpm / bun)
Workflow
Step 1 — Install packages
Pick the command that matches the project's package manager:
# npm
npm install nativewind tailwindcss react-native-reanimated react-native-safe-area-context
# yarn
yarn add nativewind tailwindcss react-native-reanimated react-native-safe-area-context
# pnpm
pnpm add nativewind tailwindcss react-native-reanimated react-native-safe-area-context
# bun
bun add nativewind tailwindcss react-native-reanimated react-native-safe-area-context
Step 2 — Create tailwind.config.js
Run the Tailwind init command:
npx tailwindcss init
Then replace the generated file with the NativeWind preset and content paths that match the project:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./App.{js,jsx,ts,tsx}",
"./app/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}",
],
presets: [require("nativewind/preset")],
theme: {
extend: {},
},
plugins: [],
};
Adjust the
contentglobs to match the actual source layout (e.g../src/**/*.{js,jsx,ts,tsx}).
Step 3 — Create global.css
Create global.css in the project root:
@tailwind base;
@tailwind components;
@tailwind utilities;
Step 4 — Configure Babel
Edit babel.config.js:
module.exports = function (api) {
api.cache(true);
return {
presets: [
["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel",
],
};
};
Step 5 — Configure Metro
Create or update metro.config.js:
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");
const config = getDefaultConfig(__dirname);
module.exports = withNativeWind(config, { input: "./global.css" });
Step 6 — Import global.css in the entry file
Add the import to the root entry file. For Expo Router projects this is app/_layout.tsx; for bare Expo projects it is App.tsx (or App.js):
import "./global.css";
Step 7 — Switch the web bundler in app.json
{
"expo": {
"web": {
"bundler": "metro"
}
}
}
Step 8 — Clear caches and restart
npx expo start --clear
Step 9 — Verify
Replace the root view with a quick smoke test:
import { Text, View } from "react-native";
export default function App() {
return (
<View className="flex-1 items-center justify-center bg-white">
<Text className="text-2xl font-bold text-blue-500">NativeWind works!</Text>
</View>
);
}
If the text renders blue and centered, the setup is complete.
Output Format
After completing all steps, confirm:
-
nativewindandtailwindcssappear inpackage.jsondependencies -
tailwind.config.jscontainspresets: [require("nativewind/preset")] -
global.cssexists with all three@tailwinddirectives -
babel.config.jsusesjsxImportSource: "nativewind"and"nativewind/babel" -
metro.config.jswraps config withwithNativeWind -
global.cssis imported in the entry file -
app.jsonsetsweb.bundlerto"metro"
Optional Topics
See REFERENCE.md for:
- TypeScript setup
- Dark mode (system preference + manual toggle)
- Custom fonts with expo-font
- NX monorepo wiring
Common Pitfalls
| Problem | Fix |
|---|---|
| Classes compile but styles don't apply | Confirm global.css is imported in the entry file |
| Metro error on startup | Delete .expo/ and node_modules cache, re-run npx expo start --clear |
| Web styles missing | Ensure app.json has "bundler": "metro" under expo.web |
TypeScript errors on className |
Add /// <reference types="nativewind/types" /> — see REFERENCE.md |
Monorepo: withNativeWind not a function |
Use promise-chain pattern — see REFERENCE.md |