A comprehensive theming system for React Native applications that supports multiple color themes with light/dark mode variants, built with NativeWind & Zustand.
- Distinct Color Themes: Default Abyss, Fiery Ember, Malignant Moss, Crimson Bloom, Golden Hour, and Abyssal Depths
- Light/Dark Mode Support: Each theme has both light and dark variants
- Persistent Storage: Theme preferences are saved across app sessions
- Semantic Color System: Colors are organized by semantic meaning (background, text, stroke, etc.)
- React Navigation Integration: Automatic theme application to navigation components
- Type-Safe: Full TypeScript support with proper type definitions
- Cross-Platform: Works on iOS, Android, and Web
The theming system follows a layered architecture:
User Interaction (Components)
β
State Management (Zustand Store)
β
Theme Provider (CSS Variables)
β
Color System (Semantic Tokens)
β
Primitive Colors (Base Palette)
Purpose: The foundation of the theming system containing all color definitions.
Structure:
- Primitives: Base color palettes (AbyssalDepths, CrimsonBloom, EmberGlow, etc.) with scales from 0-900
- Semantic Mappings: Maps primitive colors to semantic meanings (background-void, text-rich, brand-primary-base)
- Theme Definitions: Combines semantic mappings for each theme in light/dark variants
Key Features:
- Common semantic colors for light/dark modes
buildBrand()function to create consistent brand color mappings- 6 theme variants with primary/secondary color combinations
Purpose: React context provider that applies the current theme using CSS variables.
Functionality:
- Reads current theme and color scheme from Zustand store
- Converts theme object to CSS variables using NativeWind's
vars()function - Wraps the app with theme-aware styling
- Integrates with React Navigation's theme provider
Purpose: Zustand store for managing theme state.
State:
theme: Current theme name (default, ember, moss, etc.)colorScheme: Current mode (light/dark)- Actions for updating theme and color scheme
Purpose: Main hook for theme management with persistence.
Features:
- Loads saved theme preferences on app start
- Persists changes to SecureStore (mobile) or localStorage (web)
- Synchronizes with NativeWind's color scheme
- Provides convenient setters for theme and mode changes
- Cross-platform storage handling
Purpose: Simple hook to access current theme's color values.
Returns: The complete color object for the current theme and mode combination.
Purpose: Generates React Navigation theme configuration.
Functionality:
- Maps semantic colors to React Navigation's expected color structure
- Dynamically updates based on current theme and mode
- Provides proper navigation theming
Purpose: UI component for selecting color themes.
Features:
- Displays color swatches for each available theme
- Shows current selection with border highlighting
- Supports accessibility labels and states
- Callback support for theme selection events
Purpose: Radio button component for light/dark mode selection.
Features:
- Custom radio button design
- Accessibility support
- Visual feedback for current selection
Purpose: Configuration for theme selector UI.
Contains:
- Theme metadata (names, labels, CSS classes)
- Used by ThemeSelector component to render options
Purpose: Utility for merging Tailwind classes.
Functionality:
- Combines
clsxandtailwind-merge - Handles conditional classes and deduplication
Purpose: Tailwind CSS configuration that integrates the color system.
Features:
- Dynamically generates color classes from semantic tokens
- Maps color keys to CSS variables
- Includes primitive colors for direct access
- Configured for React Native with NativeWind preset
App Start β useSelectedTheme loads saved preferences β Store updated β Provider applies CSS vars
User selects theme β ThemeSelector calls setSelectedTheme β Store updated β Provider re-renders with new CSS vars
User toggles mode β ThemeModeToggle calls setSelectedScheme β Store + NativeWind updated β Provider re-renders
Component needs colors β useThemeColors reads from store β Returns current theme's color object
// Using semantic classes
<View className="bg-surface-primary border-stroke-subtle">
<Text className="text-text-void">Hello World</Text>
</View>const themeColors = useThemeColors();
<View style={{ backgroundColor: themeColors["brand-primary-base"] }}>
<Text style={{ color: themeColors["text-void"] }}>Styled Text</Text>
</View>;const { selectedTheme, setSelectedTheme } = useSelectedTheme();
// Change theme
await setSelectedTheme("crimson");Background Colors: background-void to background-pure
- Used for different levels of background prominence
Text Colors: text-void to text-pure
- Hierarchical text emphasis levels
Stroke Colors: stroke-void to stroke-pure
- Border and divider colors
Brand Colors: brand-primary-* and brand-secondary-*
- Theme-specific accent colors
Surface Colors: surface-primary and surface-secondary
- Card and container backgrounds
Static Colors: static-white and static-black
- Colors that flips between modes
- Default Abyss - Navy blue primary, orange secondary
- Fiery Ember - Orange primary, gray secondary
- Abyssal Depths - Deep blue primary, gray secondary
- Malignant Moss - Green primary, gray secondary
- Crimson Bloom - Red primary, gray secondary
- Golden Hour - Yellow primary, gray secondary
- Add primitive colors in
utils/colors.ts:
const primitives = {
// ... existing colors
NewColorName: {
0: "#ffffff",
50: "#f0f0f0",
// ... define 100-900
},
};- Add theme definition:
export const themes = {
// ... existing themes
newtheme: {
light: {
...commonSemanticColorsLight,
...buildBrand(primitives.NewColorName, primitives.ObsidianWhisper),
},
dark: {
...commonSemanticColorsDark,
...buildBrand(primitives.NewColorName, primitives.ObsidianWhisper),
},
},
};- Update theme selector in
utils/themes.ts:
export const themes = [
// ... existing themes
{
name: "newtheme",
label: "New Theme",
colorClass: "bg-NewColorName-500",
borderColorClass: "border-NewColorName-500",
},
];- Update TypeScript types in
providers/theme-provider.tsxif needed.
Theme preferences are automatically saved and restored:
- Mobile: Uses Expo SecureStore for secure storage
- Web: Uses localStorage for browser storage
- Loading: Preferences load on app initialization
- Fallbacks: Graceful handling of missing or invalid stored values
The theming system includes comprehensive accessibility support:
- Proper ARIA labels and roles
- Keyboard navigation support
- Screen reader compatibility
- High contrast considerations in color selection
- Semantic HTML structure
Themes automatically apply to:
- Navigation headers
- Tab bars
- Background colors
- Text colors
- Border colors
- CSS variables for dynamic theming
- Class-based styling with semantic names
- Responsive design support
- Dark mode integration
This theming system provides a robust, scalable foundation for building visually cohesive React Native applications with multiple theme options and accessibility support.