Theme Providers
Overview
Section titled “Overview”Sparkle provides two specialized theme providers to manage theme state and make design tokens available throughout your application:
ThemeProvider- For web applications using React DOMNativeThemeProvider- For React Native mobile applications
Both providers offer the same core functionality but with platform-specific optimizations and features.
Related Pages
Section titled “Related Pages”- Theme Overview - Introduction to Sparkle’s theme system
- Token Transformation - Cross-platform token conversion
- Complete Workflow - End-to-end implementation guide
- Advanced Customization - Multi-brand and enterprise patterns
- Troubleshooting - Provider setup issues and solutions
Core Functionality
Section titled “Core Functionality”Shared Features
Section titled “Shared Features”Both theme providers provide:
- Theme switching - Change between light, dark, and system themes
- Persistence - Remember user’s theme preference across sessions
- System detection - Automatically follow system color scheme
- Validation - Ensure theme configurations are valid
- Error handling - Graceful fallbacks when themes fail to load
- Type safety - Full TypeScript support with design token types
Platform-Specific Features
Section titled “Platform-Specific Features”Web (ThemeProvider):
- CSS custom properties injection
localStoragepersistencematchMediasystem detection- SSR/hydration support
React Native (NativeThemeProvider):
AsyncStoragepersistenceAppearanceAPI system detection- StatusBar style integration
- Platform-specific fallbacks
ThemeProvider (Web)
Section titled “ThemeProvider (Web)”Basic Setup
Section titled “Basic Setup”import {ThemeProvider} from '@sparkle/theme'
function App() { return ( <ThemeProvider defaultTheme="system"> <YourAppContent /> </ThemeProvider> )}Complete Configuration
Section titled “Complete Configuration”import {darkTokens, lightTokens, ThemeProvider} from '@sparkle/theme'
function App() { return ( <ThemeProvider // Default theme mode on first load defaultTheme="light" // Custom theme configurations themes={{ light: lightTokens, dark: darkTokens, }} // Persistence settings storageKey="my-app-theme" // System theme detection disableSystemTheme={false} // CSS variable injection target cssSelector=":root" > <YourAppContent /> </ThemeProvider> )}Props Reference
Section titled “Props Reference”interface ThemeProviderProps { /** Child components that will have access to the theme context */ children: ReactNode
/** Default theme mode to use on first load (default: 'system') */ defaultTheme?: ThemeMode
/** Custom theme configurations to override defaults */ themes?: ThemeCollection
/** Storage key for persisting theme preference (default: 'sparkle-theme') */ storageKey?: string
/** Whether to disable system theme detection (default: false) */ disableSystemTheme?: boolean
/** CSS selector for injecting CSS variables (default: ':root') */ cssSelector?: string}CSS Variables Integration
Section titled “CSS Variables Integration”When you use ThemeProvider, CSS custom properties are automatically injected into the DOM:
/* Automatically generated by ThemeProvider */:root { --color-primary-500: #3b82f6; --color-secondary-500: #64748b; --spacing-md: 1rem; --font-size-base: 1rem; /* ... all theme tokens */}
/* In dark mode */:root { --color-primary-500: #60a5fa; --color-secondary-500: #94a3b8; /* ... dark theme tokens */}Use these variables directly in your CSS:
.button { background-color: var(--color-primary-500); color: var(--color-text-inverse); padding: var(--spacing-md); border-radius: var(--border-radius-md);}Server-Side Rendering (SSR)
Section titled “Server-Side Rendering (SSR)”The ThemeProvider handles SSR scenarios gracefully:
// Next.js exampleimport {ThemeProvider} from '@sparkle/theme'
function MyApp({Component, pageProps}) { return ( <ThemeProvider defaultTheme="light"> <Component {...pageProps} /> </ThemeProvider> )}
// The provider will:// 1. Use defaultTheme during SSR// 2. Hydrate with stored preference on client// 3. Prevent hydration mismatchesNativeThemeProvider (React Native)
Section titled “NativeThemeProvider (React Native)”Basic Setup
Section titled “Basic Setup”import {NativeThemeProvider} from '@sparkle/theme'
function App() { return ( <NativeThemeProvider defaultTheme="system"> <YourAppContent /> </NativeThemeProvider> )}Complete Configuration
Section titled “Complete Configuration”import {darkTokens, lightTokens, NativeThemeProvider} from '@sparkle/theme'
function App() { return ( <NativeThemeProvider // Default theme mode on first load defaultTheme="light" // Custom theme configurations themes={{ light: lightTokens, dark: darkTokens, }} // Persistence settings storageKey="my-app-theme" // System theme detection disableSystemTheme={false} // StatusBar integration updateStatusBar={true} > <YourAppContent /> </NativeThemeProvider> )}Props Reference
Section titled “Props Reference”interface NativeThemeProviderProps { /** Child components that will have access to the theme context */ children: ReactNode
/** Default theme mode to use on first load (default: 'system') */ defaultTheme?: ThemeMode
/** Custom theme configurations to override defaults */ themes?: ThemeCollection
/** Storage key for persisting theme preference (default: 'sparkle-theme') */ storageKey?: string
/** Whether to disable system theme detection (default: false) */ disableSystemTheme?: boolean
/** Whether to automatically update StatusBar style based on theme (default: true) */ updateStatusBar?: boolean}StatusBar Integration
Section titled “StatusBar Integration”The NativeThemeProvider automatically manages StatusBar styling:
// Automatically handles StatusBar based on themeimport {StatusBar} from 'expo-status-bar'
function App() { return ( <NativeThemeProvider> {/* StatusBar style is automatically set based on theme */} <StatusBar style="auto" /> <YourAppContent /> </NativeThemeProvider> )}
// Light theme: StatusBar style = 'dark'// Dark theme: StatusBar style = 'light'AsyncStorage Integration
Section titled “AsyncStorage Integration”Theme preferences are automatically persisted using AsyncStorage:
// No additional setup required - persistence is automaticimport {NativeThemeProvider} from '@sparkle/theme'
function App() { return ( <NativeThemeProvider storageKey="my-custom-key"> <YourAppContent /> </NativeThemeProvider> )}
// Theme changes are automatically saved to AsyncStorage// On app restart, the last selected theme is restoredUsing Themes in Components
Section titled “Using Themes in Components”useTheme Hook
Section titled “useTheme Hook”Both providers expose the same useTheme hook interface:
import {useTheme} from '@sparkle/theme'
function ThemedComponent() { const { theme, // Current theme configuration object activeTheme, // Current theme mode: 'light' | 'dark' | 'system' setTheme, // Function to change theme systemTheme, // Detected system theme: 'light' | 'dark' isLoading, // Loading state during initialization error, // Error state if theme loading fails } = useTheme()
// Access design tokens const primaryColor = theme.colors.primary[500] const spacing = theme.spacing.md
// Change theme const switchToLight = () => setTheme('light') const switchToDark = () => setTheme('dark') const followSystem = () => setTheme('system')
if (isLoading) { return <div>Loading theme...</div> }
if (error) { return <div>Error loading theme: {error.message}</div> }
return ( <div style={{color: primaryColor}}> <p>Current theme: {activeTheme}</p> <p>System prefers: {systemTheme}</p> <button onClick={switchToLight}>Light</button> <button onClick={switchToDark}>Dark</button> <button onClick={followSystem}>System</button> </div> )}Theme Context Type
Section titled “Theme Context Type”The theme context provides the following interface:
interface ThemeContextValue { /** Current theme configuration with all design tokens */ theme: ThemeConfig
/** Currently active theme mode */ activeTheme: ThemeMode
/** Function to change the theme */ setTheme: (theme: ThemeMode) => void | Promise<void>
/** System-detected color scheme preference */ systemTheme: SystemColorScheme
/** Whether the theme is currently loading */ isLoading: boolean
/** Error state if theme loading/validation fails */ error: Error | null}Advanced Usage
Section titled “Advanced Usage”Custom Theme Collections
Section titled “Custom Theme Collections”Define your own theme variants:
import {baseTokens, ThemeProvider} from '@sparkle/theme'
// Create custom theme variantsconst customThemes = { light: { ...baseTokens, colors: { ...baseTokens.colors, primary: {500: '#0066cc'}, // Custom blue }, }, dark: { ...baseTokens, colors: { ...baseTokens.colors, primary: {500: '#3399ff'}, // Lighter blue for dark mode background: {primary: '#1a1a1a'}, }, }, // Add more themes highContrast: { ...baseTokens, colors: { ...baseTokens.colors, primary: {500: '#000000'}, background: {primary: '#ffffff'}, }, },}
function App() { return ( <ThemeProvider themes={customThemes} defaultTheme="light"> <YourAppContent /> </ThemeProvider> )}Theme Switching Components
Section titled “Theme Switching Components”Create reusable theme controls:
import {useTheme} from '@sparkle/theme'
function ThemeToggle() { const {activeTheme, setTheme, systemTheme} = useTheme()
const cycleTheme = () => { switch (activeTheme) { case 'light': setTheme('dark') break case 'dark': setTheme('system') break case 'system': setTheme('light') break } }
const getButtonText = () => { switch (activeTheme) { case 'light': return '☀️ Light' case 'dark': return '🌙 Dark' case 'system': return `🔄 System (${systemTheme})` } }
return <button onClick={cycleTheme}>{getButtonText()}</button>}
function ThemeSelect() { const {activeTheme, setTheme} = useTheme()
return ( <select value={activeTheme} onChange={(e) => setTheme(e.target.value as ThemeMode)}> <option value="light">Light</option> <option value="dark">Dark</option> <option value="system">Follow System</option> </select> )}Conditional Rendering Based on Theme
Section titled “Conditional Rendering Based on Theme”import {useTheme} from '@sparkle/theme'
function ConditionalThemedComponent() { const {activeTheme, systemTheme} = useTheme()
// Get the resolved theme (accounting for system preference) const resolvedTheme = activeTheme === 'system' ? systemTheme : activeTheme
return ( <div> {resolvedTheme === 'dark' ? ( <DarkModeSpecificComponent /> ) : ( <LightModeSpecificComponent /> )}
{/* Show different icons based on theme */} <img src={resolvedTheme === 'dark' ? '/logo-dark.png' : '/logo-light.png'} alt="Logo" /> </div> )}Migration Between Platforms
Section titled “Migration Between Platforms”Shared Theme Logic
Section titled “Shared Theme Logic”Extract theme logic to share between web and React Native:
import {baseTokens} from '@sparkle/theme'
export const appThemes = { light: { ...baseTokens, colors: { ...baseTokens.colors, primary: {500: '#007AFF'}, // iOS blue }, }, dark: { ...baseTokens, colors: { ...baseTokens.colors, primary: {500: '#0A84FF'}, // iOS dark blue background: {primary: '#000000'}, }, },}
// Custom hook for theme switching logicexport function useAppTheme() { const {theme, setTheme, activeTheme} = useTheme()
const toggleTheme = () => { setTheme(activeTheme === 'light' ? 'dark' : 'light') }
return {theme, setTheme, activeTheme, toggleTheme}}import {ThemeProvider} from '@sparkle/theme'import {appThemes} from '../shared/theme'
function WebApp() { return ( <ThemeProvider themes={appThemes}> <YourWebApp /> </ThemeProvider> )}import {NativeThemeProvider} from '@sparkle/theme'import {appThemes} from '../shared/theme'
function NativeApp() { return ( <NativeThemeProvider themes={appThemes}> <YourNativeApp /> </NativeThemeProvider> )}Best Practices
Section titled “Best Practices”1. Use Appropriate Provider for Platform
Section titled “1. Use Appropriate Provider for Platform”// ✅ Good - Use platform-specific providers// Webimport {ThemeProvider} from '@sparkle/theme'
// React Nativeimport {NativeThemeProvider} from '@sparkle/theme'
// ❌ Avoid - Don't mix platforms// Don't use ThemeProvider in React Native// Don't use NativeThemeProvider in web apps2. Handle Loading States
Section titled “2. Handle Loading States”import {useTheme} from '@sparkle/theme'
function App() { const {isLoading, error, theme} = useTheme()
if (isLoading) { return <LoadingScreen /> }
if (error) { return <ErrorScreen error={error} /> }
return <MainApp theme={theme} />}3. Minimize Theme Context Re-renders
Section titled “3. Minimize Theme Context Re-renders”import {useTheme} from '@sparkle/theme'import {memo} from 'react'
// ✅ Good - Memoize components that use themeconst ThemedButton = memo(({children}) => { const {theme} = useTheme() return ( <button style={{backgroundColor: theme.colors.primary[500]}}> {children} </button> )})
// ✅ Good - Extract only what you needfunction OptimizedComponent() { const {setTheme} = useTheme() // Only subscribe to setTheme return <button onClick={() => setTheme('dark')}>Dark Mode</button>}4. Validate Custom Themes
Section titled “4. Validate Custom Themes”import {ThemeProvider, validateTheme} from '@sparkle/theme'
const customTheme = { // ... your custom theme}
// Validate before usingconst validation = validateTheme(customTheme)if (!validation.isValid) { console.error('Theme validation failed:', validation.errors) // Handle invalid theme}
function App() { return ( <ThemeProvider themes={{light: customTheme}}> <YourApp /> </ThemeProvider> )}5. Graceful Error Handling
Section titled “5. Graceful Error Handling”import {ThemeProvider} from '@sparkle/theme'
function App() { return ( <ThemeProvider // Provide fallback theme defaultTheme="light" themes={{ light: safeTheme, dark: safeTheme, }} > <ErrorBoundary fallback={<ErrorUI />}> <YourApp /> </ErrorBoundary> </ThemeProvider> )}Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”Issue: Theme not persisting between sessions
Section titled “Issue: Theme not persisting between sessions”Web:
// Check localStorage supportif (typeof localStorage !== 'undefined') { console.log('Theme stored:', localStorage.getItem('sparkle-theme'))}React Native:
// Check AsyncStorageimport AsyncStorage from '@react-native-async-storage/async-storage'
AsyncStorage.getItem('sparkle-theme').then(value => { console.log('Theme stored:', value)})Issue: System theme not detecting
Section titled “Issue: System theme not detecting”Web:
// Check matchMedia supportif (window.matchMedia) { const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches console.log('System prefers dark:', isDark)}React Native:
// Check Appearance APIimport {Appearance} from 'react-native'
console.log('System color scheme:', Appearance.getColorScheme())Issue: CSS variables not updating
Section titled “Issue: CSS variables not updating”import {ThemeProvider} from '@sparkle/theme'
// Check if CSS variables are injected// Force CSS variable updateconst root = document.documentElementconst primaryColor = getComputedStyle(root).getPropertyValue('--color-primary-500')console.log('Primary color CSS var:', primaryColor);
<ThemeProvider cssSelector=":root" forceUpdate={true}> <App /></ThemeProvider>Performance Tips
Section titled “Performance Tips”- Memoize theme-dependent components to prevent unnecessary re-renders
- Use CSS custom properties on web for better performance than inline styles
- Pre-load theme assets (like themed images) to prevent flash during theme switches
- Debounce rapid theme changes if allowing programmatic theme switching
API Reference
Section titled “API Reference”For complete API documentation, see: