Troubleshooting & Best Practices
Overview
Section titled “Overview”This guide covers common issues you might encounter with Sparkle’s theme system, debugging strategies, performance optimization techniques, and development best practices.
Related Pages
Section titled “Related Pages”- Theme Overview - Introduction to Sparkle’s theme system
- Theme Providers - Provider setup and configuration
- Token Transformation - Cross-platform token conversion
- Complete Workflow - End-to-end implementation guide
- Advanced Customization - Enterprise patterns and custom solutions
Common Issues & Solutions
Section titled “Common Issues & Solutions”Theme Provider Issues
Section titled “Theme Provider Issues”Issue: Theme context is undefined
Section titled “Issue: Theme context is undefined”Symptoms:
// Error: Cannot read property 'theme' of undefinedconst {theme} = useTheme()Causes & Solutions:
-
Missing ThemeProvider wrapper:
// ❌ Bad: No ThemeProviderfunction App() {return <MyComponent />}// ✅ Good: Proper ThemeProvider setupfunction App() {return (<ThemeProvider><MyComponent /></ThemeProvider>)} -
Using hook outside provider scope:
// ❌ Bad: Hook used outside providerfunction App() {const {theme} = useTheme() // Error!return (<ThemeProvider><MyComponent /></ThemeProvider>)}// ✅ Good: Hook used inside providerfunction App() {return (<ThemeProvider><MyComponent /> {/* useTheme() works here */}</ThemeProvider>)} -
Multiple providers creating scope issues:
// ❌ Bad: Nested providers can cause confusion<ThemeProvider theme="light"><SomeComponent /><ThemeProvider theme="dark"> {/* Creates separate context */}<AnotherComponent /></ThemeProvider></ThemeProvider>// ✅ Good: Single provider with dynamic theme switchingfunction App() {const [theme, setTheme] = useState('light')return (<ThemeProvider theme={theme}><SomeComponent /><AnotherComponent /></ThemeProvider>)}
Issue: Platform-specific provider errors
Section titled “Issue: Platform-specific provider errors”React Native: “undefined is not an object”
// ❌ Bad: Using web ThemeProvider in React Nativeimport {ThemeProvider} from '@sparkle/theme'
// ✅ Good: Use platform-specific providerimport {NativeThemeProvider} from '@sparkle/theme'
function App() { return ( <NativeThemeProvider> <MyNativeComponent /> </NativeThemeProvider> )}Token Transformation Issues
Section titled “Token Transformation Issues”Issue: Tokens not updating after transformation
Section titled “Issue: Tokens not updating after transformation”Cause: Transform cache not invalidated
// ❌ Bad: Cached result not updatedconst transformer = new TokenTransformer()const webTokens = transformer.toWeb(tokens) // Cached// ... tokens modified ...const updatedTokens = transformer.toWeb(tokens) // Still cached!
// ✅ Good: Clear cache or disable cachingconst transformer = new TokenTransformer({enableCache: false})// ORtransformer.clearCache()const updatedTokens = transformer.toWeb(tokens)Issue: Performance degradation with large token sets
Section titled “Issue: Performance degradation with large token sets”Debugging transformation performance:
// Add performance monitoringconst transformer = new TokenTransformer({ enableCache: true, debug: true, // Enables performance logging})
console.time('Token transformation')const result = transformer.toWeb(largeTokenSet)console.timeEnd('Token transformation')
// Check cache effectivenessconsole.log('Cache stats:', transformer.getCacheStats())Optimization strategies:
// 1. Use caching for repeated transformationsconst transformer = new TokenTransformer({ enableCache: true, maxCacheSize: 100, // Limit cache size})
// 2. Transform only necessary tokensconst minimalTokens = { colors: fullTokens.colors, spacing: fullTokens.spacing, // Don't include unused token categories}
// 3. Use batch transformation for multiple themesconst themes = ['light', 'dark', 'highContrast']const transformedThemes = transformer.batchTransform(themes, baseTokens)CSS Custom Properties Issues
Section titled “CSS Custom Properties Issues”Issue: CSS variables not updating
Section titled “Issue: CSS variables not updating”Browser debugging steps:
-
Check DevTools Elements panel:
/* Should see variables in :root */:root {--color-primary: #3b82f6;--spacing-md: 1rem;} -
Verify theme application:
/* Check if theme classes are applied */[data-theme="dark"] {--color-primary: #60a5fa;} -
Test variable resolution:
/* Test in DevTools console */getComputedStyle(document.documentElement).getPropertyValue('--color-primary')
Common solutions:
// ✅ Ensure CSS is injected properlyimport '@sparkle/ui/styles.css'
// ✅ Force theme updateconst {setTheme} = useTheme()setTheme('dark') // Should trigger CSS variable updates// ✅ Check for CSS specificity issues.my-component { /* Use !important if needed for debugging */ color: var(--color-primary) !important;}Issue: SSR hydration mismatches
Section titled “Issue: SSR hydration mismatches”Symptoms: Different themes rendered on server vs client
// ❌ Bad: No SSR handlingfunction App() { return ( <ThemeProvider theme="system"> {/* 'system' not available on server */} <MyComponent /> </ThemeProvider> )}
// ✅ Good: SSR-safe theme setupfunction App() { return ( <ThemeProvider theme="light" enableSystem={false} // Disable system detection on SSR disableTransitionOnChange // Prevent flash during hydration > <MyComponent /> </ThemeProvider> )}
// ✅ Better: Client-side theme detectionfunction App() { const [mounted, setMounted] = useState(false)
useEffect(() => { setMounted(true) }, [])
if (!mounted) { return ( <ThemeProvider theme="light"> <MyComponent /> </ThemeProvider> ) }
return ( <ThemeProvider theme="system"> <MyComponent /> </ThemeProvider> )}TypeScript Issues
Section titled “TypeScript Issues”Issue: Theme token types not recognized
Section titled “Issue: Theme token types not recognized”Symptoms:
// Error: Property 'primary' does not exist on type 'Colors'const primaryColor = theme.colors.primary[500]Solutions:
-
Ensure proper type imports:
import type {ThemeConfig} from '@sparkle/types' -
Extend theme types for custom tokens:
types/theme.d.ts declare module '@sparkle/types' {interface Colors {brand: {50: string100: string500: string900: string}}} -
Use type assertions for dynamic tokens:
const customColor = (theme.colors as any).customColor?.[500] || '#fallback'
Performance Optimization
Section titled “Performance Optimization”Theme Transformation Performance
Section titled “Theme Transformation Performance”Optimize Token Structure
Section titled “Optimize Token Structure”// ❌ Bad: Deeply nested tokens slow transformationconst inefficientTokens = { components: { button: { variants: { primary: { states: { default: { background: { color: { value: '#3b82f6' } } } } } } } }}
// ✅ Good: Flatter structure for better performanceconst efficientTokens = { colors: { buttonPrimary: '#3b82f6', buttonSecondary: '#6b7280', }, components: { button: { primary: 'var(--color-button-primary)', secondary: 'var(--color-button-secondary)', } }}Cache Strategy
Section titled “Cache Strategy”// Create singleton transformer instance// Use memoization for expensive operationsimport {useMemo} from 'react'
export const globalTransformer = new TokenTransformer({ enableCache: true, maxCacheSize: 50, // Reasonable cache size})
function useTransformedTokens(tokens: ThemeConfig) { return useMemo(() => { return globalTransformer.toWeb(tokens) }, [tokens])}Component Performance
Section titled “Component Performance”Avoid Unnecessary Re-renders
Section titled “Avoid Unnecessary Re-renders”// ❌ Bad: Creates new object on every renderfunction MyComponent() { const {theme} = useTheme() const styles = { color: theme.colors.primary[500], padding: theme.spacing.md, } return <div style={styles}>Content</div>}
// ✅ Good: Memoized stylesfunction MyComponent() { const {theme} = useTheme() const styles = useMemo(() => ({ color: theme.colors.primary[500], padding: theme.spacing.md, }), [theme.colors.primary, theme.spacing.md])
return <div style={styles}>Content</div>}
// ✅ Better: CSS-in-JS with static classesfunction MyComponent() { return <div className="text-primary p-md">Content</div>}Optimize Theme Context
Section titled “Optimize Theme Context”// ❌ Bad: Large context object causes unnecessary re-rendersconst ThemeContext = createContext({ theme: fullThemeObject, // Huge object setTheme: () => {}, allTokens: [], // Rarely used transformedTokens: {}, // Heavy computation})
// ✅ Good: Split context by usage patternsconst ThemeContext = createContext({ theme: currentTheme, setTheme: () => {},})
const TokenContext = createContext({ tokens: transformedTokens,})
// Use separate hooksfunction useTheme() { return useContext(ThemeContext)}
function useTokens() { return useContext(TokenContext)}Bundle Size Optimization
Section titled “Bundle Size Optimization”Tree Shaking
Section titled “Tree Shaking”// ❌ Bad: Imports entire theme packageimport * as theme from '@sparkle/theme'
// ✅ Good: Import only what you needimport {TokenTransformer} from '@sparkle/theme'import {useTheme} from '@sparkle/theme/hooks'Lazy Loading
Section titled “Lazy Loading”// Lazy load platform-specific transformersconst PlatformTransformer = lazy(() => import('@sparkle/theme/transformers/platform'))
// Conditional importsconst transformer = process.env.NODE_ENV === 'development' ? await import('@sparkle/theme/debug') : await import('@sparkle/theme/production')Development Best Practices
Section titled “Development Best Practices”Theme Definition
Section titled “Theme Definition”Organize Tokens Logically
Section titled “Organize Tokens Logically”// ✅ Good: Organized by purpose and scaleexport const designTokens = { // Primitive tokens (design decisions) colors: { blue: { 50: '#eff6ff', 500: '#3b82f6', 900: '#1e3a8a', }, },
// Semantic tokens (usage-based) semantic: { colors: { primary: 'var(--color-blue-500)', background: 'var(--color-blue-50)', text: 'var(--color-blue-900)', }, },
// Component tokens (specific usage) components: { button: { background: 'var(--semantic-color-primary)', text: 'var(--semantic-color-text)', }, },}Use Consistent Naming
Section titled “Use Consistent Naming”// ✅ Good: Consistent naming conventionconst tokens = { colors: { // Scale-based naming primary: {50: '...', 100: '...', 500: '...', 900: '...'}, secondary: {50: '...', 100: '...', 500: '...', 900: '...'},
// Semantic naming text: { primary: '...', secondary: '...', disabled: '...', },
// State-based naming success: {light: '...', DEFAULT: '...', dark: '...'}, error: {light: '...', DEFAULT: '...', dark: '...'}, },
spacing: { // T-shirt sizing xs: '0.25rem', sm: '0.5rem', md: '1rem', lg: '1.5rem', xl: '2rem', },}Testing Strategies
Section titled “Testing Strategies”Theme Testing
Section titled “Theme Testing”// Test theme transformationsdescribe('TokenTransformer', () => { test('transforms tokens correctly', () => { const transformer = new TokenTransformer() const result = transformer.toWeb(mockTokens)
expect(result).toEqual({ '--color-primary': '#3b82f6', '--spacing-md': '1rem', }) })
test('handles cache correctly', () => { const transformer = new TokenTransformer({enableCache: true}) const firstCall = transformer.toWeb(mockTokens) const secondCall = transformer.toWeb(mockTokens)
expect(firstCall).toBe(secondCall) // Same reference due to cache })})
// Test theme contextdescribe('ThemeProvider', () => { test('provides theme context', () => { const TestComponent = () => { const {theme} = useTheme() return <div data-testid="theme">{theme}</div> }
render( <ThemeProvider theme="dark"> <TestComponent /> </ThemeProvider> )
expect(screen.getByTestId('theme')).toHaveTextContent('dark') })})Visual Testing
Section titled “Visual Testing”// Storybook story for theme testingexport const ThemeComparison = () => { const themes = ['light', 'dark', 'highContrast']
return ( <div style={{display: 'flex', gap: '1rem'}}> {themes.map(theme => ( <div key={theme} data-theme={theme}> <h3>{theme}</h3> <Button variant="primary">Primary Button</Button> <Button variant="secondary">Secondary Button</Button> </div> ))} </div> )}
// Chromatic visual testingexport default { title: 'Themes/Comparison', component: ThemeComparison, parameters: { chromatic: { // Test multiple themes modes: { light: {theme: 'light'}, dark: {theme: 'dark'}, } } }}Documentation Standards
Section titled “Documentation Standards”Document Token Purpose
Section titled “Document Token Purpose”/** * Primary color palette for brand consistency. * Used for primary actions, links, and brand elements. * * @example * ```tsx * <Button style={{backgroundColor: 'var(--color-primary-500)'}}> * Primary Action * </Button> * ``` */export const primaryColors = { 50: '#eff6ff', 100: '#dbeafe', 500: '#3b82f6', // Brand primary 600: '#2563eb', // Hover state 900: '#1e3a8a',}Provide Usage Examples
Section titled “Provide Usage Examples”/** * Semantic spacing tokens for consistent layout. * * @example Layout spacing * ```tsx * <div style={{ * padding: 'var(--spacing-md)', * margin: 'var(--spacing-lg)', * }}> * Content with consistent spacing * </div> * ``` * * @example Component spacing * ```tsx * <Button style={{ * paddingInline: 'var(--spacing-lg)', * paddingBlock: 'var(--spacing-sm)', * }}> * Well-spaced button * </Button> * ``` */export const spacing = { sm: '0.5rem', md: '1rem', lg: '1.5rem',}Debugging Techniques
Section titled “Debugging Techniques”Theme Inspector
Section titled “Theme Inspector”// Debug utility for theme inspectionexport function createThemeInspector() { if (process.env.NODE_ENV !== 'development') { return () => {} // No-op in production }
return { logCurrentTheme() { const root = document.documentElement const theme = root.dataset.theme const computedStyles = getComputedStyle(root)
console.group(`🎨 Current Theme: ${theme}`)
// Log all CSS custom properties const cssVars = {} for (const property of computedStyles) { if (property.startsWith('--')) { cssVars[property] = computedStyles.getPropertyValue(property) } }
console.table(cssVars) console.groupEnd() },
visualizeTokens(tokens: ThemeConfig) { console.group('🎯 Token Structure') console.log('Colors:', tokens.colors) console.log('Spacing:', tokens.spacing) console.log('Typography:', tokens.typography) console.groupEnd() },
compareThemes(theme1: ThemeConfig, theme2: ThemeConfig) { const differences = []
function compare(obj1: any, obj2: any, path = '') { Object.keys(obj1).forEach(key => { const currentPath = path ? `${path}.${key}` : key
if (obj1[key] !== obj2[key]) { differences.push({ path: currentPath, theme1: obj1[key], theme2: obj2[key], }) } }) }
compare(theme1, theme2) console.table(differences) } }}
// Usage in developmentconst themeInspector = createThemeInspector()themeInspector.logCurrentTheme()Performance Profiling
Section titled “Performance Profiling”// Performance monitoring hookexport function useThemePerformance() { const {theme} = useTheme() const [stats, setStats] = useState<{ renderCount: number lastRenderTime: number averageRenderTime: number }>({ renderCount: 0, lastRenderTime: 0, averageRenderTime: 0, })
useEffect(() => { const start = performance.now()
return () => { const end = performance.now() const renderTime = end - start
setStats(prev => ({ renderCount: prev.renderCount + 1, lastRenderTime: renderTime, averageRenderTime: (prev.averageRenderTime * prev.renderCount + renderTime) / (prev.renderCount + 1), })) } }, [theme])
return stats}
// Component usagefunction MyComponent() { const stats = useThemePerformance()
if (process.env.NODE_ENV === 'development') { console.log('Theme render stats:', stats) }
return <div>Component content</div>}This troubleshooting guide provides comprehensive coverage of common issues, performance optimization strategies, development best practices, and debugging techniques for Sparkle’s theme system.