
Styling Nativewind V4 Expo
- 362 installs
- 3 repo stars
- Updated June 29, 2026
- tristanmanchester/agent-skills
styling-nativewind-v4-expo is a Claude Code skill that configures and troubleshoots NativeWind v4 with Tailwind CSS v3 in Expo React Native apps for developers who need className utilities working on iOS, Android, and we
About
styling-nativewind-v4-expo is a mobile styling skill from tristanmanchester/agent-skills focused on NativeWind v4 with Tailwind CSS v3 inside Expo React Native projects, including Expo Router. It enforces a three-path setup—one global.css entry, matching metro.config.js input, and an app entry import—and configures tailwind.config.js with nativewind/preset, babel.config.js with jsxImportSource and nativewind/babel preset, and metro.config.js with withNativeWind. The skill includes a ten-step quick-start checklist, deterministic config audit prompt, troubleshooting workflow ordered around npx expo start --clear, and patterns for dark mode via useColorScheme, cssInterop for third-party components, and safe-area utilities. Reach for styling-nativewind-v4-expo when className styles fail to apply, Metro cache causes stale Tailwind output, or a new Expo app needs NativeWind wired correctly across iOS, Android, and web targets.
- styling-nativewind-v4-expo
Styling Nativewind V4 Expo by the numbers
- 362 all-time installs (skills.sh)
- +3 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #1,170 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/tristanmanchester/agent-skills --skill styling-nativewind-v4-expoAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 362 |
|---|---|
| repo stars | ★ 3 |
| Last updated | June 29, 2026 |
| Repository | tristanmanchester/agent-skills ↗ |
How do you fix NativeWind v4 className issues?
Use styling-nativewind-v4-expo for development tasks
Who is it for?
React Native developers building Expo apps who need NativeWind v4 Tailwind className utilities working across iOS, Android, and web.
Skip if: Plain React Native without Expo, web-only Tailwind projects, or teams already on NativeWind v2 migration paths unrelated to v4.
When should I use this skill?
className styles fail to apply in an Expo app, NativeWind v4 setup is new, or Metro cache and Tailwind CLI compilation errors appear.
What you get
Verified tailwind.config.js, global.css, babel.config.js, metro.config.js, app.json web bundler settings, and nativewind-env.d.ts TypeScript declarations.
- tailwind.config.js
- metro.config.js with withNativeWind
- working className smoke test screen
By the numbers
- Includes a 10-step quick-start checklist for NativeWind v4 Expo setup
- Configures 5 core files: tailwind.config.js, global.css, babel.config.js, metro.config.js, app.json
Files
NativeWind v4 for Expo (React Native)
Non‑negotiables (v4)
- Use Tailwind CSS v3 and include
presets: [require("nativewind/preset")]intailwind.config.js. - Keep exactly one Tailwind entry CSS file (commonly
global.css) and keep its path consistent across: metro.config.js→withNativeWind(..., { input: "./global.css" })- your app entry →
import "./global.css"(orimport "../global.css"fromapp/_layout.tsx) - Keep
nativewind/babelin Babel `presets` and setjsxImportSource: "nativewind"onbabel-preset-expo. - After any config change, restart Metro without cache:
npx expo start --clear.
Quick start checklist
Copy/paste and tick off:
- [ ] Install deps (NativeWind + Tailwind + peers). See
references/expo-setup.md. - [ ] Create/verify
tailwind.config.js(content globs +nativewind/preset). - [ ] Create/verify
global.csswith Tailwind directives. - [ ] Create/verify
babel.config.js(jsxImportSource +nativewind/babel). - [ ] Create/verify
metro.config.js(wrap config withwithNativeWind, setinput). - [ ] If targeting web, set
app.json→expo.web.bundler = "metro". - [ ] If TypeScript, add
nativewind-env.d.tswith/// <reference types="nativewind/types" />. - [ ] Start with cache cleared and validate on-device + web:
npx expo start --clear. - [ ] Validate with an obvious “smoke test” screen: background colour + centred text.
Project type selection
- Expo Router: entry is usually
app/_layout.tsx→ import CSS there (relative path is typically../global.css). - Classic: entry is usually
App.tsx→ import CSS there (./global.css).
If unsure, search package.json for "main": "expo-router/entry".
Implementation patterns
Build reusable components (recommended)
Accept className, merge defaults, and optionally use a class-variance helper.
Read: references/patterns.md
Style third‑party components (only when necessary)
Use remapProps (multiple style props) or cssInterop (map a class prop to a style prop).
Read: references/third-party-components.md
Dark mode + theming
Use useColorScheme / colorScheme.set() and CSS variables via vars().
Read: references/theming-dark-mode.md
Safe area utilities
On Expo Router, do not add your own SafeAreaProvider (Router already does). Use p-safe, pt-safe, etc.
If you are not using Expo Router, wrap the root with SafeAreaProvider.
Troubleshooting workflow (always in this order)
1. Start Expo without cache: npx expo start --clear. 2. Verify Tailwind CLI works by compiling your CSS entry file to an output file. 3. Confirm the “three paths” match:
- CSS file exists
metro.config.jsinputpoints to it- your app imports it from the entry component
4. Confirm tailwind.config.js content globs include every directory that contains className strings. 5. Only then debug platform-specific behaviour (web bundler, Router, safe area, etc).
Read: references/troubleshooting.md
THE EXACT PROMPT — NativeWind v4 config audit
Use this prompt to perform a deterministic audit of an existing repo:
You are auditing an Expo React Native repo for NativeWind v4 correctness.
1) Identify whether the project uses Expo Router (app/ directory + package.json main = expo-router/entry) or classic App.tsx.
2) Check and report on:
- tailwind.config.js: presets + content globs
- global.css: Tailwind directives exist
- babel.config.js: jsxImportSource nativewind + nativewind/babel in presets; preserve any existing required plugins
- metro.config.js: withNativeWind wrapper; input path matches the CSS file
- app.json: web bundler metro when web is used
- TypeScript: nativewind-env.d.ts present and correctly named
3) For every issue, propose the minimal diff needed to fix it.
4) End by listing the exact commands to restart Metro and validate the fix.Expo setup templates (NativeWind v4)
Use these as “known-good” templates. Always adapt content globs and CSS import paths to the repo.0) New project shortcut (Expo)
If you are creating a brand new Expo project and want to skip manual setup, use:
npx rn-new --nativewind1) Install dependencies
Minimum set:
nativewindtailwindcss(dev dependency)react-native-reanimatedreact-native-safe-area-contextprettier-plugin-tailwindcss(optional dev dependency)
Notes:
- Prefer
npx expo installfor Expo-managed native libraries if the repo uses Expo SDK pinning. - If the repo already pins
react-native-reanimated/react-native-safe-area-context, do not “fight” the existing versions unless you have a clear incompatibility.
2) tailwind.config.js
Classic Expo (App.tsx entry)
/** @type {import('tailwindcss').Config} */
module.exports = {
// NOTE: Update this to include the paths to all files that contain Nativewind classes.
content: ["./App.{js,jsx,ts,tsx}", "./components/**/*.{js,jsx,ts,tsx}"],
presets: [require("nativewind/preset")],
theme: { extend: {} },
plugins: [],
};Expo Router (app/ directory)
/** @type {import('tailwindcss').Config} */
module.exports = {
// NOTE: Update this to include the paths to all files that contain Nativewind classes.
content: [
"./App.{js,jsx,ts,tsx}",
"./app/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}",
],
presets: [require("nativewind/preset")],
theme: { extend: {} },
plugins: [],
};3) global.css
Create a single CSS entry file (commonly global.css) and add Tailwind directives:
@tailwind base;
@tailwind components;
@tailwind utilities;If you choose a different filename/location, use that same relative path in BOTH metro.config.js input and your root import.
4) babel.config.js (Expo)
module.exports = function (api) {
api.cache(true);
return {
presets: [["babel-preset-expo", { jsxImportSource: "nativewind" }], "nativewind/babel"],
};
};If the repo already has a babel.config.js, merge carefully:
- Keep
babel-preset-expoas the base preset. - Keep existing plugins/presets required by other tooling.
- Keep
nativewind/babelinpresets(notplugins).
5) metro.config.js (Expo)
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");
const config = getDefaultConfig(__dirname);
module.exports = withNativeWind(config, { input: "./global.css" });6) app.json (web support)
If the project targets web, set Metro as the web bundler:
{
"expo": {
"web": {
"bundler": "metro"
}
}
}7) Import the CSS entry file
Classic Expo (App.tsx)
import "./global.css";Expo Router (app/_layout.tsx)
import "../global.css";Only import the CSS once at the top of the entry component.
8) TypeScript types (optional)
Create nativewind-env.d.ts:
/// <reference types="nativewind/types" />Do not name it nativewind.d.ts, and avoid naming collisions like app.d.ts when an /app directory exists.
Component + className patterns
Default styles + className merge (simple)
Use when you are building app-level components (buttons, cards, typography).
import { Text } from "react-native";
type Props = {
className?: string;
children: React.ReactNode;
};
export function Label({ className = "", children }: Props) {
const defaults = "text-black dark:text-white";
return <Text className={`${defaults} ${className}`}>{children}</Text>;
}Variants (manual mapping)
Use when you need a small number of variants and want zero dependencies.
import { Pressable, Text } from "react-native";
const base = "rounded-md px-4 py-2";
const variants: Record<"primary" | "secondary", string> = {
primary: "bg-blue-600 active:bg-blue-700",
secondary: "bg-zinc-200 dark:bg-zinc-800",
};
const label: Record<"primary" | "secondary", string> = {
primary: "text-white font-semibold",
secondary: "text-zinc-900 dark:text-zinc-50 font-medium",
};
export function Button({
variant = "primary",
className = "",
labelClassName = "",
children,
...props
}: React.ComponentProps<typeof Pressable> & {
variant?: "primary" | "secondary";
labelClassName?: string;
}) {
return (
<Pressable className={`${base} ${variants[variant]} ${className}`} {...props}>
<Text className={`${label[variant]} ${labelClassName}`}>{children}</Text>
</Pressable>
);
}Variants (recommended in larger apps)
If variants are growing complex, use a class name management library:
tailwind-variantscvaclsx/classnames
NativeWind v4 keeps className available inside components, so these libraries work well.
Theming + dark mode (NativeWind v4)
Dark mode: system vs manual
- Read system preference via
useColorScheme(). - Allow user override via
colorScheme.set("light" | "dark" | "system").
Expo note: to follow system appearance, set userInterfaceStyle to automatic in app.json.
Minimal toggle
import { useState } from "react";
import { Pressable, Text, View } from "react-native";
import { colorScheme } from "nativewind";
export function ThemeToggle() {
const [current, setCurrent] = useState<"light" | "dark">("light");
return (
<View className="items-center">
<Pressable
onPress={() => {
const next = current === "light" ? "dark" : "light";
setCurrent(next);
colorScheme.set(next);
}}
>
<Text className="font-semibold">{current}</Text>
</Pressable>
</View>
);
}Dynamic themes with CSS variables
1) Define colours in tailwind.config.js
Create theme colours that point at CSS variables:
module.exports = {
theme: {
colors: {
primary: "rgb(var(--color-primary) / <alpha-value>)",
secondary: "rgb(var(--color-secondary) / <alpha-value>)",
},
},
};Set defaults on :root (via Tailwind addBase) if you want stable defaults:
plugins: [
({ addBase }) =>
addBase({
":root": {
"--color-primary": "0 0 0",
"--color-secondary": "255 255 255",
},
}),
];2) Override variables at runtime with vars()
import { vars, useColorScheme } from "nativewind";
import { Text, View } from "react-native";
const themes = {
brand: {
light: vars({ "--color-primary": "0 0 0", "--color-secondary": "255 255 255" }),
dark: vars({ "--color-primary": "255 255 255", "--color-secondary": "24 24 27" }),
},
};
export function ThemedCard({ children }: { children: React.ReactNode }) {
const { colorScheme } = useColorScheme();
return (
<View style={themes.brand[colorScheme]} className="rounded-xl bg-secondary p-4">
<Text className="text-primary">{children}</Text>
</View>
);
}Guideline: keep your “theme contract” small (a handful of variables), then build design tokens around it.
Third‑party components (remapProps / cssInterop)
Decision guide
- Prefer plain `className` when the component forwards
classNamecorrectly. - Use `remapProps` when a component has multiple style props and you want
*Classprops. - Use `cssInterop` when you need to map a class prop to a style prop (and optionally extract style attributes into non-style props).
Avoid using these for your own app components; for your own components, accept className and merge it.
remapProps (multiple style props)
import { remapProps } from "nativewind";
/**
* ThirdPartyButton has buttonStyle + labelStyle
*/
const StyledThirdPartyButton = remapProps(ThirdPartyButton, {
buttonClass: "buttonStyle",
labelClass: "labelStyle",
});
// Usage
<StyledThirdPartyButton buttonClass="bg-blue-500" labelClass="text-white" />;Notes:
remapProps(component, { "new-prop": "existing-prop" })creates a new prop and maps it.remapProps(component, { prop: true })overrides an existing prop.
cssInterop (map className → style, extract attributes)
Example based on TextInput patterns:
import { TextInput } from "react-native";
import { cssInterop } from "nativewind";
cssInterop(TextInput, {
className: {
target: "style",
nativeStyleToProp: {
textAlign: true,
},
},
placeholderClassName: {
target: false,
nativeStyleToProp: {
color: "placeholderTextColor",
},
},
selectionClassName: {
target: false,
nativeStyleToProp: {
color: "selectionColor",
},
},
});Guidelines:
- Call
cssInterop(...)once (at app entry) before the component is rendered. - Keep mappings tiny and explicit; do not “globally interop everything”.
Editor IntelliSense for custom props (VS Code)
If you introduce custom class props (e.g. headerClassName), extend Tailwind IntelliSense:
{
"tailwindCSS.classAttributes": ["class", "className", "headerClassName"]
}Troubleshooting NativeWind v4 in Expo
Always start here
1) Restart Metro without cache:
npx expo start --clear2) Confirm Tailwind CLI works independently of NativeWind:
npx tailwindcss --input ./global.css --output output.cssIf the class you expect is not present in output.css, your issue is Tailwind config/content, not NativeWind runtime.
Quick checks (90% of issues)
“className not applying” / everything looks unstyled
- Confirm
global.cssexists. - Confirm
metro.config.jswithNativeWind(..., { input: "./global.css" })points to it. - Confirm the entry component imports it exactly once.
- Confirm
tailwind.config.jscontentglobs include all files that containclassNamestrings: ./App.{js,jsx,ts,tsx}./app/**/*.{js,jsx,ts,tsx}(Expo Router)./components/**/*.{js,jsx,ts,tsx}- Clear cache and restart.
Web looks unstyled
- Confirm
app.jsonsets: expo.web.bundler = "metro"
Verify the NativeWind install from inside the app
NativeWind exposes a helper:
import { verifyInstallation } from "nativewind";
export default function App() {
verifyInstallation(); // call inside component scope, not globally
return null;
}Remove it after you confirm the setup.
Enable debug logs
Run your start command with DEBUG=nativewind:
DEBUG=nativewind npx expo start --clearCommon React Native gotchas (looks like “NativeWind is broken”)
Colour classes don’t “cascade”
React Native doesn’t cascade text colour from a <View> to a nested <Text>. Move text-* classes onto the <Text> element.
Conditional styles
React Native can behave oddly when styles appear/disappear. Prefer explicit style pairs (both light + dark) rather than only setting one side.
Safe area utilities not working
- If you are not using Expo Router, ensure you wrap the root in
SafeAreaProvider. - If you are using Expo Router, do not add another provider; Router already wraps routes.
Expo Router breaks after adding babel/metro config
If the Router stops finding routes, confirm:
- Babel still uses
babel-preset-expoas the base preset. - You restarted with
npx expo start --clear.
If you see EXPO_ROUTER_APP_ROOT missing, consult Expo Router troubleshooting and ensure your entry point/babel config still matches Router expectations.
Related skills
How it compares
Pick styling-nativewind-v4-expo over generic React Native styling skills when debugging NativeWind v4's three-path CSS setup rather than StyleSheet or styled-components patterns.
FAQ
What files does styling-nativewind-v4-expo configure?
styling-nativewind-v4-expo configures tailwind.config.js with nativewind/preset, global.css with Tailwind directives, babel.config.js with jsxImportSource nativewind, metro.config.js with withNativeWind input path, app.json web bundler metro, and nativewind-env.d.ts for TypeScrip
Why do NativeWind className styles fail in Expo?
styling-nativewind-v4-expo diagnoses the most common cause: mismatched paths between global.css, metro.config.js input, and the app entry import. The skill restarts Metro without cache using npx expo start --clear and verifies Tailwind CLI compiles the CSS entry file.
Does styling-nativewind-v4-expo support Expo Router?
styling-nativewind-v4-expo supports both Expo Router and classic App.tsx entry points. For Router projects, global.css is imported from app/_layout.tsx, typically with a ../global.css relative path after detecting expo-router/entry in package.json.