Styling
This boilerplate uses NativeWind 4, bringing Tailwind CSS to React Native with full TypeScript support and excellent developer experience.
Why NativeWind?
⚡ Familiar Syntax
Use the same Tailwind classes you know from web development
🎨 Design System
Consistent spacing, colors, and typography out of the box
🌙 Dark Mode
Built-in dark mode with dark: prefix
📱 Responsive
Responsive utilities: sm:, md:, lg:
Basic Usage
typescript
import { View, Text } from 'react-native';
export function WelcomeScreen() {
return (
<View className="flex-1 bg-white dark:bg-slate-900 p-4">
<Text className="text-2xl font-bold text-slate-900 dark:text-white mb-2">
Welcome!
</Text>
<Text className="text-slate-600 dark:text-slate-400">
Get started with NativeWind
</Text>
</View>
);
}NativeWind compiles Tailwind classes to React Native styles at build time for optimal performance.
Common Patterns
Layout & Flexbox
typescript
// Container with padding
<View className="flex-1 p-6 bg-slate-50 dark:bg-slate-900">
// Centered content
<View className="flex-1 items-center justify-center">
// Row layout
<View className="flex-row items-center gap-3">
// Space between items
<View className="flex-row justify-between items-center">
// Column with gap
<View className="flex-col gap-4">Text Styling
typescript
// Headings
<Text className="text-3xl font-bold text-slate-900 dark:text-white">
Title
</Text>
// Body text
<Text className="text-base text-slate-700 dark:text-slate-300">
Description
</Text>
// Small text
<Text className="text-sm text-slate-500 dark:text-slate-400">
Caption
</Text>
// Multiline with spacing
<Text className="text-base leading-relaxed">
Lorem ipsum dolor sit amet
</Text>Buttons
typescript
import { Pressable, Text } from 'react-native';
// Primary button
<Pressable className="bg-blue-500 px-6 py-3 rounded-lg active:bg-blue-600">
<Text className="text-white font-semibold text-center">
Submit
</Text>
</Pressable>
// Secondary button
<Pressable className="bg-slate-200 dark:bg-slate-700 px-6 py-3 rounded-lg">
<Text className="text-slate-900 dark:text-white font-semibold text-center">
Cancel
</Text>
</Pressable>
// Outline button
<Pressable className="border-2 border-blue-500 px-6 py-3 rounded-lg">
<Text className="text-blue-500 font-semibold text-center">
Learn More
</Text>
</Pressable>Cards
typescript
<View className="bg-white dark:bg-slate-800 rounded-xl p-6 shadow-sm">
<Text className="text-xl font-bold text-slate-900 dark:text-white mb-2">
Card Title
</Text>
<Text className="text-slate-600 dark:text-slate-400">
Card content goes here
</Text>
</View>
// Card with border
<View className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl p-6">
{/* Content */}
</View>Lists
typescript
import { FlatList } from 'react-native';
<FlatList
data={items}
contentContainerClassName="p-4 gap-3"
renderItem={({ item }) => (
<View className="bg-white dark:bg-slate-800 p-4 rounded-lg">
<Text className="font-semibold text-slate-900 dark:text-white">
{item.title}
</Text>
<Text className="text-sm text-slate-600 dark:text-slate-400 mt-1">
{item.description}
</Text>
</View>
)}
/>Responsive Design
NativeWind supports responsive breakpoints:
typescript
// Default: base (mobile)
// sm: 640px
// md: 768px
// lg: 1024px
<View className="w-full md:w-1/2 lg:w-1/3">
{/* Full width on mobile, half on tablet, 1/3 on desktop */}
</View>
<Text className="text-base sm:text-lg md:text-xl">
Responsive text
</Text>
<View className="p-4 md:p-6 lg:p-8">
{/* Increasing padding on larger screens */}
</View>Dark Mode
Dark mode is built-in with the dark: prefix:
typescript
import { useColorScheme } from 'react-native';
export function ThemedScreen() {
const colorScheme = useColorScheme(); // 'light' or 'dark'
return (
<View className="flex-1 bg-white dark:bg-slate-900">
<Text className="text-slate-900 dark:text-white">
Hello World
</Text>
<View className="bg-slate-100 dark:bg-slate-800 p-4 rounded-lg">
<Text className="text-slate-700 dark:text-slate-300">
This adapts to system theme
</Text>
</View>
</View>
);
}NativeWind automatically detects system theme. No need to manually toggle!
Custom Theme
Extend the default theme in your Tailwind config:
tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
presets: [require('nativewind/preset')],
theme: {
extend: {
colors: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
500: '#3b82f6',
600: '#2563eb',
900: '#1e3a8a',
},
secondary: {
500: '#10b981',
600: '#059669',
},
},
fontFamily: {
sans: ['Inter', 'system-ui'],
mono: ['JetBrains Mono'],
},
spacing: {
'18': '4.5rem',
'88': '22rem',
},
},
},
plugins: [],
};typescript
// Use custom colors
<View className="bg-primary-500">
<Text className="text-secondary-600">Custom theme!</Text>
</View>Reusable Components
Create styled components with variants:
src/presentation/components/Button.tsx
import { Pressable, Text, ActivityIndicator } from 'react-native';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/utils/cn';
const buttonVariants = cva(
'rounded-lg px-6 py-3 active:opacity-80 transition-opacity',
{
variants: {
variant: {
primary: 'bg-blue-500',
secondary: 'bg-slate-200 dark:bg-slate-700',
outline: 'border-2 border-blue-500 bg-transparent',
ghost: 'bg-transparent',
},
size: {
sm: 'px-4 py-2',
md: 'px-6 py-3',
lg: 'px-8 py-4',
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
}
);
const textVariants = cva('font-semibold text-center', {
variants: {
variant: {
primary: 'text-white',
secondary: 'text-slate-900 dark:text-white',
outline: 'text-blue-500',
ghost: 'text-blue-500',
},
size: {
sm: 'text-sm',
md: 'text-base',
lg: 'text-lg',
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
});
interface ButtonProps extends VariantProps<typeof buttonVariants> {
onPress: () => void;
children: string;
isLoading?: boolean;
disabled?: boolean;
className?: string;
}
export function Button({
onPress,
children,
variant,
size,
isLoading,
disabled,
className,
}: ButtonProps) {
return (
<Pressable
onPress={onPress}
disabled={disabled || isLoading}
className={cn(
buttonVariants({ variant, size }),
disabled && 'opacity-50',
className
)}
>
{isLoading ? (
<ActivityIndicator color={variant === 'primary' ? 'white' : 'black'} />
) : (
<Text className={textVariants({ variant, size })}>{children}</Text>
)}
</Pressable>
);
}
// Usage
<Button variant="primary" size="lg" onPress={handleSubmit}>
Submit
</Button>
<Button variant="outline" onPress={handleCancel}>
Cancel
</Button>Conditional Styling
typescript
import { cn } from '@/utils/cn';
function TaskItem({ task, isSelected }) {
return (
<View
className={cn(
'p-4 rounded-lg',
task.completed && 'opacity-50',
isSelected ? 'bg-blue-100 dark:bg-blue-900' : 'bg-white dark:bg-slate-800'
)}
>
<Text
className={cn(
'text-base',
task.completed && 'line-through text-slate-500'
)}
>
{task.title}
</Text>
</View>
);
}Animations
Combine NativeWind with React Native Reanimated:
typescript
import Animated, {
useAnimatedStyle,
withSpring,
} from 'react-native-reanimated';
function AnimatedCard() {
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: withSpring(isPressed ? 0.95 : 1) }],
}));
return (
<Animated.View
style={animatedStyle}
className="bg-white dark:bg-slate-800 rounded-xl p-6 shadow-lg"
>
{/* Content */}
</Animated.View>
);
}Forms
typescript
import { TextInput } from 'react-native';
<View className="gap-4">
<View>
<Text className="text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Email
</Text>
<TextInput
className="bg-white dark:bg-slate-800 border border-slate-300 dark:border-slate-600 rounded-lg px-4 py-3 text-slate-900 dark:text-white"
placeholder="you@example.com"
placeholderTextColor="#94a3b8"
/>
</View>
<View>
<Text className="text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Password
</Text>
<TextInput
className="bg-white dark:bg-slate-800 border border-slate-300 dark:border-slate-600 rounded-lg px-4 py-3 text-slate-900 dark:text-white"
placeholder="••••••••"
placeholderTextColor="#94a3b8"
secureTextEntry
/>
</View>
</View>Platform-Specific Styles
typescript
import { Platform } from 'react-native';
<View className={cn(
'p-4',
Platform.OS === 'ios' ? 'pt-12' : 'pt-6'
)}>
{/* Content */}
</View>
// Or use inline
<View style={{ paddingTop: Platform.OS === 'ios' ? 48 : 24 }}>
{/* Content */}
</View>Best Practices
✅ Use semantic class names
Prefer gap-4 over manual spacing for consistency
✅ Create reusable components
Extract common patterns into components with variants
✅ Support dark mode
Always include dark: variants for better UX
✅ Test on real devices
Simulators don't always match real device appearance
Not all Tailwind classes work in React Native. Stick to flexbox, colors, spacing, and typography.