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.

Next Steps