Light & Dark Themes
Veltix provides built-in light and dark themes with automatic system preference detection and seamless switching.
Overview
The light and dark theme system includes:
- Automatic Detection: Detect system theme preferences
- Manual Switching: Toggle between light and dark themes
- Consistent Styling: Unified design across all components
- Accessibility: WCAG compliant color contrasts
- Smooth Transitions: Animated theme switching
Theme Switching
Automatic Theme Detection
import { useTheme } from '@veltix/ui';
function ThemeAwareComponent() {
const { theme, setTheme, isDark, isSystem } = useTheme();
return (
<div className={`theme-${theme}`}>
<h1>Current Theme: {theme}</h1>
<p>Is Dark: {isDark ? 'Yes' : 'No'}</p>
<p>Using System: {isSystem ? 'Yes' : 'No'}</p>
<div className="theme-controls">
<button onClick={() => setTheme('light')}>Light</button>
<button onClick={() => setTheme('dark')}>Dark</button>
<button onClick={() => setTheme('auto')}>Auto</button>
</div>
</div>
);
}System Theme Detection
// Detect system theme preference
const useSystemTheme = () => {
const [systemTheme, setSystemTheme] = useState('light');
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleChange = (e) => {
setSystemTheme(e.matches ? 'dark' : 'light');
};
// Set initial theme
setSystemTheme(mediaQuery.matches ? 'dark' : 'light');
// Listen for changes
mediaQuery.addEventListener('change', handleChange);
return () => {
mediaQuery.removeEventListener('change', handleChange);
};
}, []);
return systemTheme;
};Light Theme
Light Theme Colors
const lightTheme = {
colors: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a'
},
background: {
primary: '#ffffff',
secondary: '#f8fafc',
tertiary: '#f1f5f9'
},
text: {
primary: '#0f172a',
secondary: '#475569',
tertiary: '#64748b',
inverse: '#ffffff'
},
border: {
primary: '#e2e8f0',
secondary: '#cbd5e1',
focus: '#3b82f6'
},
chart: {
axis: '#64748b',
grid: '#e2e8f0',
text: '#475569'
}
}
};Light Theme Components
// Light theme component styles
const lightComponentStyles = {
card: {
backgroundColor: '#ffffff',
borderColor: '#e2e8f0',
shadow: '0 1px 3px rgba(0, 0, 0, 0.1)'
},
button: {
primary: {
backgroundColor: '#3b82f6',
color: '#ffffff',
borderColor: '#3b82f6'
},
secondary: {
backgroundColor: 'transparent',
color: '#3b82f6',
borderColor: '#3b82f6'
}
},
input: {
backgroundColor: '#ffffff',
borderColor: '#e2e8f0',
textColor: '#0f172a',
placeholderColor: '#64748b'
}
};Dark Theme
Dark Theme Colors
const darkTheme = {
colors: {
primary: {
50: '#0f172a',
100: '#1e293b',
200: '#334155',
300: '#475569',
400: '#64748b',
500: '#94a3b8',
600: '#cbd5e1',
700: '#e2e8f0',
800: '#f1f5f9',
900: '#f8fafc'
},
background: {
primary: '#0f172a',
secondary: '#1e293b',
tertiary: '#334155'
},
text: {
primary: '#f8fafc',
secondary: '#cbd5e1',
tertiary: '#94a3b8',
inverse: '#0f172a'
},
border: {
primary: '#334155',
secondary: '#475569',
focus: '#60a5fa'
},
chart: {
axis: '#94a3b8',
grid: '#334155',
text: '#cbd5e1'
}
}
};Dark Theme Components
// Dark theme component styles
const darkComponentStyles = {
card: {
backgroundColor: '#1e293b',
borderColor: '#334155',
shadow: '0 1px 3px rgba(0, 0, 0, 0.3)'
},
button: {
primary: {
backgroundColor: '#60a5fa',
color: '#0f172a',
borderColor: '#60a5fa'
},
secondary: {
backgroundColor: 'transparent',
color: '#60a5fa',
borderColor: '#60a5fa'
}
},
input: {
backgroundColor: '#1e293b',
borderColor: '#334155',
textColor: '#f8fafc',
placeholderColor: '#94a3b8'
}
};Theme Switching Animation
Smooth Transitions
// Theme transition styles
const themeTransitionStyles = {
transition: 'all 0.3s ease-in-out',
transitionProperties: [
'background-color',
'color',
'border-color',
'box-shadow'
]
};
// CSS for smooth transitions
const themeCSS = `
* {
transition: background-color 0.3s ease-in-out,
color 0.3s ease-in-out,
border-color 0.3s ease-in-out,
box-shadow 0.3s ease-in-out;
}
.theme-transition {
transition: all 0.3s ease-in-out;
}
`;Animated Theme Toggle
// Animated theme toggle component
function AnimatedThemeToggle() {
const { theme, setTheme, isDark } = useTheme();
const [isAnimating, setIsAnimating] = useState(false);
const handleThemeToggle = () => {
setIsAnimating(true);
// Add transition class
document.body.classList.add('theme-transition');
// Toggle theme
setTheme(isDark ? 'light' : 'dark');
// Remove transition class after animation
setTimeout(() => {
document.body.classList.remove('theme-transition');
setIsAnimating(false);
}, 300);
};
return (
<button
onClick={handleThemeToggle}
disabled={isAnimating}
className="theme-toggle"
>
{isDark ? '☀️' : '🌙'}
</button>
);
}Accessibility
Color Contrast
// WCAG compliant color combinations
const accessibleColors = {
light: {
primary: '#3b82f6', // AA compliant
text: '#0f172a', // AAA compliant
background: '#ffffff' // AAA compliant
},
dark: {
primary: '#60a5fa', // AA compliant
text: '#f8fafc', // AAA compliant
background: '#0f172a' // AAA compliant
}
};
// Contrast ratio checker
const checkContrast = (color1, color2) => {
// Calculate contrast ratio
const ratio = calculateContrastRatio(color1, color2);
return {
ratio,
isAACompliant: ratio >= 4.5,
isAAACompliant: ratio >= 7
};
};High Contrast Mode
// High contrast theme support
const highContrastTheme = {
light: {
primary: '#000000',
background: '#ffffff',
text: '#000000',
border: '#000000'
},
dark: {
primary: '#ffffff',
background: '#000000',
text: '#ffffff',
border: '#ffffff'
}
};
// Detect high contrast preference
const useHighContrast = () => {
const [isHighContrast, setIsHighContrast] = useState(false);
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-contrast: high)');
setIsHighContrast(mediaQuery.matches);
const handleChange = (e) => {
setIsHighContrast(e.matches);
};
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}, []);
return isHighContrast;
};Theme Persistence
Local Storage
// Theme persistence with local storage
const usePersistentTheme = () => {
const [theme, setTheme] = useState(() => {
const saved = localStorage.getItem('veltix-theme');
return saved || 'auto';
});
const updateTheme = useCallback((newTheme) => {
setTheme(newTheme);
localStorage.setItem('veltix-theme', newTheme);
}, []);
return { theme, setTheme: updateTheme };
};Server-side Persistence
// Server-side theme persistence
const useServerTheme = (userId) => {
const { theme, setTheme } = useTheme();
const saveThemeToServer = useCallback(async (newTheme) => {
try {
await fetch('/api/user/theme', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ theme: newTheme })
});
} catch (error) {
console.error('Failed to save theme:', error);
}
}, []);
const loadThemeFromServer = useCallback(async () => {
try {
const response = await fetch('/api/user/theme');
const { theme: serverTheme } = await response.json();
setTheme(serverTheme);
} catch (error) {
console.error('Failed to load theme:', error);
}
}, [setTheme]);
return { theme, setTheme: saveThemeToServer, loadTheme: loadThemeFromServer };
};Component Theming
Chart Components
// Theme-aware chart components
const useThemedChart = () => {
const { isDark } = useTheme();
const chartTheme = useMemo(() => ({
colors: {
axis: isDark ? '#94a3b8' : '#64748b',
grid: isDark ? '#334155' : '#e2e8f0',
text: isDark ? '#cbd5e1' : '#475569'
},
backgroundColor: isDark ? '#1e293b' : '#ffffff',
textColor: isDark ? '#f8fafc' : '#0f172a'
}), [isDark]);
return chartTheme;
};
// Using themed chart
function ThemedChart({ data }) {
const chartTheme = useThemedChart();
return (
<BarChart
data={data}
theme={chartTheme}
height={300}
/>
);
}UI Components
// Theme-aware UI components
const useThemedComponent = (componentType) => {
const { isDark } = useTheme();
const componentTheme = useMemo(() => {
const themes = {
card: {
light: {
backgroundColor: '#ffffff',
borderColor: '#e2e8f0',
shadow: '0 1px 3px rgba(0, 0, 0, 0.1)'
},
dark: {
backgroundColor: '#1e293b',
borderColor: '#334155',
shadow: '0 1px 3px rgba(0, 0, 0, 0.3)'
}
},
button: {
light: {
primary: '#3b82f6',
text: '#ffffff'
},
dark: {
primary: '#60a5fa',
text: '#0f172a'
}
}
};
return themes[componentType][isDark ? 'dark' : 'light'];
}, [componentType, isDark]);
return componentTheme;
};Best Practices
1. Color Usage
- Use semantic color names
- Ensure sufficient contrast ratios
- Test themes in different lighting conditions
- Consider color blindness accessibility
2. Component Design
- Design components to work with both themes
- Use CSS variables for dynamic theming
- Test components in both themes
- Maintain visual hierarchy
3. Performance
- Minimize theme switching overhead
- Use efficient CSS variables
- Cache theme configurations
- Optimize theme assets
4. User Experience
- Provide smooth theme transitions
- Remember user preferences
- Support system theme detection
- Include theme toggle controls
Troubleshooting
Common Issues
Theme not switching
- Check theme provider setup
- Verify CSS variable definitions
- Ensure proper theme registration
- Check browser storage permissions
Colors not updating
- Verify CSS variable usage
- Check theme configuration
- Ensure proper CSS specificity
- Test in different browsers
Performance issues
- Optimize theme switching
- Use efficient CSS variables
- Minimize theme complexity
- Implement proper caching
Last updated on