Theme Customization

Customize your app's theme by extending Tailwind configuration and creating design tokens.

Tailwind Configuration

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: {
        // Brand colors
        primary: {
          50: '#eff6ff',
          100: '#dbeafe',
          200: '#bfdbfe',
          300: '#93c5fd',
          400: '#60a5fa',
          500: '#3b82f6',
          600: '#2563eb',
          700: '#1d4ed8',
          800: '#1e40af',
          900: '#1e3a8a',
          950: '#172554',
        },
        secondary: {
          50: '#f0fdf4',
          100: '#dcfce7',
          500: '#10b981',
          600: '#059669',
          900: '#064e3b',
        },
        // Semantic colors
        success: '#10b981',
        warning: '#f59e0b',
        error: '#ef4444',
        info: '#3b82f6',
      },
      spacing: {
        '18': '4.5rem',
        '88': '22rem',
        '128': '32rem',
      },
      borderRadius: {
        '4xl': '2rem',
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif'],
        mono: ['JetBrains Mono', 'monospace'],
      },
      fontSize: {
        '2xs': ['0.625rem', { lineHeight: '0.75rem' }],
      },
      boxShadow: {
        'soft': '0 2px 15px -3px rgba(0, 0, 0, 0.07), 0 10px 20px -2px rgba(0, 0, 0, 0.04)',
      },
    },
  },
  plugins: [],
};

Design Tokens

Create a centralized theme file for your design system:

src/theme/tokens.ts
export const theme = {
  colors: {
    primary: '#3b82f6',
    secondary: '#10b981',
    background: {
      light: '#ffffff',
      dark: '#0f172a',
    },
    text: {
      primary: {
        light: '#0f172a',
        dark: '#f1f5f9',
      },
      secondary: {
        light: '#64748b',
        dark: '#94a3b8',
      },
    },
    border: {
      light: '#e2e8f0',
      dark: '#334155',
    },
  },
  spacing: {
    xs: 4,
    sm: 8,
    md: 16,
    lg: 24,
    xl: 32,
    '2xl': 48,
  },
  borderRadius: {
    sm: 4,
    md: 8,
    lg: 12,
    xl: 16,
    full: 9999,
  },
  typography: {
    h1: {
      fontSize: 32,
      lineHeight: 40,
      fontWeight: '700',
    },
    h2: {
      fontSize: 24,
      lineHeight: 32,
      fontWeight: '700',
    },
    body: {
      fontSize: 16,
      lineHeight: 24,
      fontWeight: '400',
    },
    caption: {
      fontSize: 14,
      lineHeight: 20,
      fontWeight: '400',
    },
  },
  shadows: {
    sm: {
      shadowColor: '#000',
      shadowOffset: { width: 0, height: 1 },
      shadowOpacity: 0.05,
      shadowRadius: 2,
      elevation: 2,
    },
    md: {
      shadowColor: '#000',
      shadowOffset: { width: 0, height: 4 },
      shadowOpacity: 0.1,
      shadowRadius: 8,
      elevation: 4,
    },
    lg: {
      shadowColor: '#000',
      shadowOffset: { width: 0, height: 8 },
      shadowOpacity: 0.15,
      shadowRadius: 16,
      elevation: 8,
    },
  },
} as const;

export type Theme = typeof theme;

Theme Context

Create a theme provider for dynamic theming:

src/theme/ThemeProvider.tsx
import { createContext, useContext, useState, useEffect } from 'react';
import { useColorScheme } from 'react-native';
import { storage } from '@/data/storage/mmkv';

type ThemeMode = 'light' | 'dark' | 'system';

interface ThemeContextType {
  mode: ThemeMode;
  actualTheme: 'light' | 'dark';
  setMode: (mode: ThemeMode) => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const systemTheme = useColorScheme();
  const [mode, setModeState] = useState<ThemeMode>(() => {
    const saved = storage.getString('theme.mode');
    return (saved as ThemeMode) || 'system';
  });

  const actualTheme = mode === 'system' ? systemTheme ?? 'light' : mode;

  const setMode = (newMode: ThemeMode) => {
    setModeState(newMode);
    storage.set('theme.mode', newMode);
  };

  return (
    <ThemeContext.Provider value={{ mode, actualTheme, setMode }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

Theme Selector Component

typescript
import { View, Text, Pressable } from 'react-native';
import { useTheme } from '@/theme/ThemeProvider';

export function ThemeSelector() {
  const { mode, setMode } = useTheme();

  const themes: Array<{ value: ThemeMode; label: string; icon: string }> = [
    { value: 'light', label: 'Light', icon: '☀️' },
    { value: 'dark', label: 'Dark', icon: '🌙' },
    { value: 'system', label: 'System', icon: '💻' },
  ];

  return (
    <View className="flex-row gap-2">
      {themes.map((theme) => (
        <Pressable
          key={theme.value}
          onPress={() => setMode(theme.value)}
          className={cn(
            'flex-1 p-4 rounded-lg items-center',
            mode === theme.value
              ? 'bg-blue-500'
              : 'bg-slate-200 dark:bg-slate-700'
          )}
        >
          <Text className="text-2xl mb-1">{theme.icon}</Text>
          <Text
            className={cn(
              'font-medium text-sm',
              mode === theme.value ? 'text-white' : 'text-slate-900 dark:text-white'
            )}
          >
            {theme.label}
          </Text>
        </Pressable>
      ))}
    </View>
  );
}

Custom Font Setup

Add custom fonts to your app:

bash
# Install expo-font
npx expo install expo-font
src/app/_layout.tsx
import { useFonts } from 'expo-font';
import * as SplashScreen from 'expo-splash-screen';
import { useEffect } from 'react';

SplashScreen.preventAutoHideAsync();

export default function RootLayout() {
  const [loaded, error] = useFonts({
    'Inter-Regular': require('../assets/fonts/Inter-Regular.ttf'),
    'Inter-Medium': require('../assets/fonts/Inter-Medium.ttf'),
    'Inter-SemiBold': require('../assets/fonts/Inter-SemiBold.ttf'),
    'Inter-Bold': require('../assets/fonts/Inter-Bold.ttf'),
  });

  useEffect(() => {
    if (loaded || error) {
      SplashScreen.hideAsync();
    }
  }, [loaded, error]);

  if (!loaded && !error) {
    return null;
  }

  return <Slot />;
}
tailwind.config.js
module.exports = {
  theme: {
    extend: {
      fontFamily: {
        sans: ['Inter-Regular'],
        'sans-medium': ['Inter-Medium'],
        'sans-semibold': ['Inter-SemiBold'],
        'sans-bold': ['Inter-Bold'],
      },
    },
  },
};
typescript
// Usage
<Text className="font-sans-bold text-2xl">
  Bold Text
</Text>

Color Palette Generator

Use these tools to generate color palettes:

Responsive Design Tokens

typescript
import { Dimensions } from 'react-native';

const { width } = Dimensions.get('window');

export const breakpoints = {
  sm: 640,
  md: 768,
  lg: 1024,
  xl: 1280,
};

export const responsive = {
  isSmall: width < breakpoints.sm,
  isMedium: width >= breakpoints.sm && width < breakpoints.md,
  isLarge: width >= breakpoints.md && width < breakpoints.lg,
  isXLarge: width >= breakpoints.lg,
  
  get(values: { sm?: any; md?: any; lg?: any; xl?: any; default: any }) {
    if (this.isXLarge && values.xl) return values.xl;
    if (this.isLarge && values.lg) return values.lg;
    if (this.isMedium && values.md) return values.md;
    if (this.isSmall && values.sm) return values.sm;
    return values.default;
  },
};

// Usage
const padding = responsive.get({
  sm: 8,
  md: 16,
  lg: 24,
  default: 16,
});

Themed Components

typescript
import { View } from 'react-native';
import { useTheme } from '@/theme/ThemeProvider';
import { theme } from '@/theme/tokens';

export function ThemedCard({ children }: { children: React.ReactNode }) {
  const { actualTheme } = useTheme();
  const isDark = actualTheme === 'dark';

  return (
    <View
      style={{
        backgroundColor: isDark 
          ? theme.colors.background.dark 
          : theme.colors.background.light,
        borderRadius: theme.borderRadius.lg,
        padding: theme.spacing.md,
        ...theme.shadows.md,
      }}
    >
      {children}
    </View>
  );
}

Best Practices

✅ Design system first

Define your design tokens before building components

✅ Consistent spacing

Use spacing scale (4, 8, 16, 24, 32, etc.)

✅ Accessible colors

Ensure proper contrast ratios for text (WCAG AA: 4.5:1)

✅ Test both themes

Always test light and dark modes

Use design tokens for all styling - never hardcode colors or spacing!

Next Steps