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-fontsrc/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:
- UIColors - Tailwind CSS color generator
- Coolors - Color palette generator
- Palettte - Design system colors
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!