name: nativewind version: 1.0.0 description: Tailwind CSS utility classes for React Native — universal styling tools: [Read, Edit, Write, Bash] tags: [mobile, react-native, expo, tailwind, nativewind, styling, typescript, universal] author: claude-skill-vault created: 2026-05-24
NativeWind — Tailwind CSS for React Native
Overview
NativeWind uses Tailwind CSS as a universal styling language for React Native. Write className strings with Tailwind utility classes and NativeWind compiles them to React Native StyleSheet objects at build time. v4 (current) targets Expo SDK 52+ and uses CSS variables for theming, making dark mode and custom themes effortless.
When to Use
- React Native or Expo projects where your team already knows Tailwind
- Universal apps sharing Tailwind styles across web (Next.js) and native
- Replacing StyleSheet objects with utility-first classes for faster iteration
- Expo projects needing dark mode with minimal setup
- Teams migrating from a web Tailwind codebase to add a native app
Installation / Setup
# NativeWind v4 with Expo
npx expo install nativewind tailwindcss react-native-reanimated react-native-safe-area-context
# Run the setup command (configures babel, metro, tsconfig)
npx expo customize metro.config.js
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./app/**/*.{js,jsx,ts,tsx}',
'./components/**/*.{js,jsx,ts,tsx}',
],
presets: [require('nativewind/preset')],
theme: {
extend: {
colors: {
brand: '#6366f1',
},
},
},
};
// 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' });
/* global.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
// app/_layout.tsx
import '../global.css';
// nativewind-env.d.ts (TypeScript support)
/// <reference types="nativewind/types" />
Key Patterns
Basic className Usage
import { View, Text, Pressable } from 'react-native';
function Button({ label, onPress }: { label: string; onPress: () => void }) {
return (
<Pressable
className="bg-indigo-600 rounded-xl px-6 py-3 active:opacity-70"
onPress={onPress}
>
<Text className="text-white font-semibold text-base text-center">
{label}
</Text>
</Pressable>
);
}
function Card({ title, body }: { title: string; body: string }) {
return (
<View className="bg-white dark:bg-zinc-900 rounded-2xl p-4 shadow-sm mx-4 my-2">
<Text className="text-zinc-900 dark:text-white text-lg font-bold mb-1">
{title}
</Text>
<Text className="text-zinc-500 dark:text-zinc-400 text-sm leading-relaxed">
{body}
</Text>
</View>
);
}
Dark Mode
import { useColorScheme } from 'nativewind';
function ThemeToggle() {
const { colorScheme, toggleColorScheme } = useColorScheme();
return (
<Pressable
className="flex-row items-center gap-2 p-3 rounded-lg bg-zinc-100 dark:bg-zinc-800"
onPress={toggleColorScheme}
>
<Text className="text-zinc-900 dark:text-white">
{colorScheme === 'dark' ? 'Light Mode' : 'Dark Mode'}
</Text>
</Pressable>
);
}
Conditional Classes (clsx/cn)
import { cn } from '@/lib/utils'; // clsx + twMerge helper
import { View, Text } from 'react-native';
type BadgeVariant = 'default' | 'success' | 'warning' | 'error';
function Badge({ label, variant = 'default' }: { label: string; variant?: BadgeVariant }) {
return (
<View
className={cn(
'px-2 py-0.5 rounded-full self-start',
variant === 'default' && 'bg-zinc-100 dark:bg-zinc-800',
variant === 'success' && 'bg-green-100 dark:bg-green-900',
variant === 'warning' && 'bg-amber-100 dark:bg-amber-900',
variant === 'error' && 'bg-red-100 dark:bg-red-900',
)}
>
<Text
className={cn(
'text-xs font-medium',
variant === 'default' && 'text-zinc-600 dark:text-zinc-300',
variant === 'success' && 'text-green-700 dark:text-green-300',
variant === 'warning' && 'text-amber-700 dark:text-amber-300',
variant === 'error' && 'text-red-700 dark:text-red-300',
)}
>
{label}
</Text>
</View>
);
}
// lib/utils.ts — cn helper
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Responsive Platform Classes
import { View, Text } from 'react-native';
// NativeWind v4 supports platform modifiers
function PlatformAdaptive() {
return (
<View className="ios:pt-12 android:pt-8 web:pt-4 px-4 bg-white dark:bg-black">
<Text className="ios:text-lg android:text-base font-semibold">
Platform-specific styles
</Text>
</View>
);
}
FlatList with NativeWind
import { FlatList, View, Text } from 'react-native';
interface Item { id: string; title: string; subtitle: string; }
function ItemRow({ item }: { item: Item }) {
return (
<View className="flex-row items-center px-4 py-3 border-b border-zinc-100 dark:border-zinc-800">
<View className="w-10 h-10 rounded-full bg-indigo-100 dark:bg-indigo-900 items-center justify-center mr-3">
<Text className="text-indigo-600 dark:text-indigo-300 font-bold text-base">
{item.title[0]}
</Text>
</View>
<View className="flex-1">
<Text className="text-zinc-900 dark:text-white font-medium">{item.title}</Text>
<Text className="text-zinc-500 dark:text-zinc-400 text-sm">{item.subtitle}</Text>
</View>
</View>
);
}
function ItemList({ items }: { items: Item[] }) {
return (
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <ItemRow item={item} />}
className="flex-1 bg-white dark:bg-black"
/>
);
}
Custom CSS Variables (Theming)
/* global.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--color-primary: 99 102 241; /* indigo-500 */
--color-background: 255 255 255;
--color-foreground: 24 24 27;
}
.dark {
--color-primary: 129 140 248; /* indigo-400 */
--color-background: 9 9 11;
--color-foreground: 244 244 245;
}
}
// tailwind.config.js
theme: {
extend: {
colors: {
primary: 'rgb(var(--color-primary) / <alpha-value>)',
background: 'rgb(var(--color-background) / <alpha-value>)',
foreground: 'rgb(var(--color-foreground) / <alpha-value>)',
},
},
}
Common Pitfalls
- Dynamic class strings don't work: never concatenate class strings like
"bg-" + color— Tailwind's static analyzer won't find them; usecn()with full class names or acvavariant map - Metro cache must be cleared after config changes:
npx expo start --clearafter modifyingtailwind.config.jsormetro.config.js - Not all Tailwind utilities translate to native: web-only utilities like
grid,display: inline, and CSS Grid don't work on native — use flexbox equivalents classNameprop only works on core RN primitives: third-party components don't acceptclassNameunless they spread props — wrap them in aViewwith NativeWind or usecssInterop- v4 vs v3 APIs differ significantly: v3 used
styled()HOC fromnativewind; v4 uses nativeclassNameprop — don't mix tutorials from different versions
Related Skills
tamagui— full UI kit alternative with more complete universal componentsexpo-router— routing that pairs naturally with NativeWindreact-native-reanimated— animations complementing NativeWind stylingreact-native-best-practices— React Native fundamentalstailwind-shadcn-ui-setup— web Tailwind setup for comparison
GitNexus Index
domain: mobile
tier: library
runtime: ios,android,browser
language: tsx,ts,css
framework: react-native,expo
purpose: styling