Skip to Content
Veltix
ThemesCustom Themes

Custom Themes

Veltix allows you to create and apply custom themes to match your brand identity and design requirements.

Overview

Custom themes in Veltix provide:

  • Brand Consistency: Match your brand colors and styling
  • Flexible Configuration: Customize colors, typography, and spacing
  • Theme Inheritance: Extend existing themes with custom modifications
  • Dynamic Theming: Apply themes programmatically
  • Theme Sharing: Export and share themes across projects

Creating Custom Themes

Basic Theme Structure

// Basic custom theme structure const customTheme = { name: 'brand-theme', version: '1.0.0', colors: { primary: { 50: '#f0f9ff', 100: '#e0f2fe', 200: '#bae6fd', 300: '#7dd3fc', 400: '#38bdf8', 500: '#0ea5e9', 600: '#0284c7', 700: '#0369a1', 800: '#075985', 900: '#0c4a6e' }, background: { primary: '#ffffff', secondary: '#f8fafc', tertiary: '#f1f5f9' }, text: { primary: '#0f172a', secondary: '#475569', tertiary: '#64748b' } }, typography: { fontFamily: { primary: 'Inter, sans-serif', secondary: 'Roboto, sans-serif' }, fontSize: { xs: '0.75rem', sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.25rem', '2xl': '1.5rem', '3xl': '1.875rem', '4xl': '2.25rem' } }, spacing: { xs: '0.25rem', sm: '0.5rem', md: '1rem', lg: '1.5rem', xl: '2rem', '2xl': '3rem' } };

Theme Registration

import { registerTheme, useTheme } from '@veltix/ui'; // Register custom theme registerTheme('brand-theme', customTheme); // Use custom theme function BrandedComponent() { const { setTheme } = useTheme(); const applyBrandTheme = () => { setTheme('brand-theme'); }; return ( <div> <button onClick={applyBrandTheme}> Apply Brand Theme </button> </div> ); }

Advanced Theme Configuration

Extended Theme Properties

// Advanced theme with extended properties const advancedTheme = { name: 'enterprise-theme', version: '2.0.0', colors: { primary: { 50: '#f0f9ff', 100: '#e0f2fe', 200: '#bae6fd', 300: '#7dd3fc', 400: '#38bdf8', 500: '#0ea5e9', 600: '#0284c7', 700: '#0369a1', 800: '#075985', 900: '#0c4a6e' }, secondary: { 50: '#fdf4ff', 100: '#fae8ff', 200: '#f5d0fe', 300: '#f0abfc', 400: '#e879f9', 500: '#d946ef', 600: '#c026d3', 700: '#a21caf', 800: '#86198f', 900: '#701a75' }, success: { 50: '#f0fdf4', 100: '#dcfce7', 200: '#bbf7d0', 300: '#86efac', 400: '#4ade80', 500: '#22c55e', 600: '#16a34a', 700: '#15803d', 800: '#166534', 900: '#14532d' }, warning: { 50: '#fffbeb', 100: '#fef3c7', 200: '#fde68a', 300: '#fcd34d', 400: '#fbbf24', 500: '#f59e0b', 600: '#d97706', 700: '#b45309', 800: '#92400e', 900: '#78350f' }, error: { 50: '#fef2f2', 100: '#fee2e2', 200: '#fecaca', 300: '#fca5a5', 400: '#f87171', 500: '#ef4444', 600: '#dc2626', 700: '#b91c1c', 800: '#991b1b', 900: '#7f1d1d' } }, components: { card: { borderRadius: '0.75rem', shadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)', border: '1px solid rgba(0, 0, 0, 0.05)' }, button: { borderRadius: '0.5rem', fontWeight: '600', transition: 'all 0.2s ease-in-out' }, input: { borderRadius: '0.375rem', borderWidth: '1px', focusRing: '0 0 0 3px rgba(14, 165, 233, 0.1)' } }, animations: { duration: { fast: '150ms', normal: '300ms', slow: '500ms' }, easing: { ease: 'cubic-bezier(0.4, 0, 0.2, 1)', easeIn: 'cubic-bezier(0.4, 0, 1, 1)', easeOut: 'cubic-bezier(0, 0, 0.2, 1)', easeInOut: 'cubic-bezier(0.4, 0, 0.2, 1)' } } };

Theme Inheritance

// Theme inheritance - extend existing theme const extendedTheme = { name: 'extended-brand-theme', extends: 'brand-theme', // Inherit from brand theme colors: { // Override specific colors primary: { 500: '#ff6b6b', // Custom primary color 600: '#ee5a52' }, // Add new colors accent: { 50: '#fff5f5', 100: '#fed7d7', 500: '#e53e3e', 900: '#742a2a' } }, components: { // Override component styles button: { borderRadius: '1rem', // Custom border radius fontWeight: '700' } } }; // Register extended theme registerTheme('extended-brand-theme', extendedTheme);

Dynamic Theme Generation

Programmatic Theme Creation

// Generate theme from brand colors const generateThemeFromBrand = (primaryColor, secondaryColor) => { return { name: 'generated-theme', colors: { primary: generateColorScale(primaryColor), secondary: generateColorScale(secondaryColor), background: { primary: '#ffffff', secondary: '#f8fafc', tertiary: '#f1f5f9' }, text: { primary: '#0f172a', secondary: '#475569', tertiary: '#64748b' } } }; }; // Color scale generator const generateColorScale = (baseColor) => { const colors = {}; const steps = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]; steps.forEach((step, index) => { const lightness = step === 500 ? 50 : step < 500 ? 50 + (500 - step) * 0.1 : step > 500 ? 50 - (step - 500) * 0.1 : 50; colors[step] = adjustColorLightness(baseColor, lightness); }); return colors; }; // Use dynamic theme generation const brandTheme = generateThemeFromBrand('#0ea5e9', '#d946ef'); registerTheme('dynamic-brand-theme', brandTheme);

Theme from CSS Variables

// Extract theme from CSS variables const extractThemeFromCSS = () => { const root = document.documentElement; const computedStyle = getComputedStyle(root); return { name: 'css-extracted-theme', colors: { primary: { 500: computedStyle.getPropertyValue('--color-primary').trim(), 600: computedStyle.getPropertyValue('--color-primary-600').trim() }, background: { primary: computedStyle.getPropertyValue('--color-background').trim(), secondary: computedStyle.getPropertyValue('--color-background-secondary').trim() }, text: { primary: computedStyle.getPropertyValue('--color-text').trim(), secondary: computedStyle.getPropertyValue('--color-text-secondary').trim() } } }; }; // Apply extracted theme const extractedTheme = extractThemeFromCSS(); registerTheme('extracted-theme', extractedTheme);

Theme Export and Import

Theme Export

// Export theme to JSON const exportTheme = (themeName) => { const theme = getTheme(themeName); const themeData = { name: theme.name, version: theme.version, colors: theme.colors, typography: theme.typography, components: theme.components, metadata: { createdAt: new Date().toISOString(), author: 'Veltix Team', description: 'Custom theme for brand consistency' } }; const blob = new Blob([JSON.stringify(themeData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${themeName}.json`; a.click(); URL.revokeObjectURL(url); }; // Export current theme const handleExportTheme = () => { const { theme } = useTheme(); exportTheme(theme); };

Theme Import

// Import theme from JSON const importTheme = async (file) => { try { const text = await file.text(); const themeData = JSON.parse(text); // Validate theme structure if (!themeData.name || !themeData.colors) { throw new Error('Invalid theme format'); } // Register imported theme registerTheme(themeData.name, themeData); return { success: true, theme: themeData.name }; } catch (error) { return { success: false, error: error.message }; } }; // File input for theme import function ThemeImporter() { const handleFileUpload = async (event) => { const file = event.target.files[0]; if (file) { const result = await importTheme(file); if (result.success) { console.log(`Theme ${result.theme} imported successfully`); } else { console.error('Failed to import theme:', result.error); } } }; return ( <input type="file" accept=".json" onChange={handleFileUpload} placeholder="Import theme file" /> ); }

Theme Validation

Theme Schema Validation

// Theme validation schema const themeSchema = { type: 'object', required: ['name', 'colors'], properties: { name: { type: 'string' }, version: { type: 'string' }, colors: { type: 'object', required: ['primary'], properties: { primary: { type: 'object' }, secondary: { type: 'object' }, background: { type: 'object' }, text: { type: 'object' } } }, typography: { type: 'object' }, components: { type: 'object' } } }; // Validate theme const validateTheme = (theme) => { const errors = []; // Check required properties if (!theme.name) { errors.push('Theme name is required'); } if (!theme.colors) { errors.push('Theme colors are required'); } if (!theme.colors.primary) { errors.push('Primary colors are required'); } // Check color format if (theme.colors.primary) { const primaryColors = Object.values(theme.colors.primary); const invalidColors = primaryColors.filter(color => !isValidColor(color)); if (invalidColors.length > 0) { errors.push('Invalid color values found'); } } return { isValid: errors.length === 0, errors }; }; // Color validation const isValidColor = (color) => { const s = new Option().style; s.color = color; return s.color !== ''; };

Theme Management

Theme Registry

// Theme registry management class ThemeRegistry { constructor() { this.themes = new Map(); this.activeTheme = null; } register(name, theme) { const validation = validateTheme(theme); if (!validation.isValid) { throw new Error(`Invalid theme: ${validation.errors.join(', ')}`); } this.themes.set(name, theme); return true; } get(name) { return this.themes.get(name); } list() { return Array.from(this.themes.keys()); } remove(name) { return this.themes.delete(name); } setActive(name) { if (!this.themes.has(name)) { throw new Error(`Theme ${name} not found`); } this.activeTheme = name; this.applyTheme(name); } getActive() { return this.activeTheme; } applyTheme(name) { const theme = this.get(name); if (!theme) return; // Apply theme to document const root = document.documentElement; // Apply colors Object.entries(theme.colors).forEach(([category, colors]) => { Object.entries(colors).forEach(([shade, color]) => { root.style.setProperty(`--color-${category}-${shade}`, color); }); }); // Apply typography if (theme.typography) { Object.entries(theme.typography.fontFamily).forEach(([type, font]) => { root.style.setProperty(`--font-family-${type}`, font); }); } } } // Global theme registry const themeRegistry = new ThemeRegistry();

Theme Switching with Persistence

// Theme switching with persistence const useThemeManager = () => { const [currentTheme, setCurrentTheme] = useState(() => { const saved = localStorage.getItem('veltix-custom-theme'); return saved || 'default'; }); const switchTheme = useCallback((themeName) => { try { themeRegistry.setActive(themeName); setCurrentTheme(themeName); localStorage.setItem('veltix-custom-theme', themeName); } catch (error) { console.error('Failed to switch theme:', error); } }, []); const availableThemes = useMemo(() => { return themeRegistry.list(); }, []); return { currentTheme, availableThemes, switchTheme }; }; // Theme switcher component function ThemeSwitcher() { const { currentTheme, availableThemes, switchTheme } = useThemeManager(); return ( <div className="theme-switcher"> <label htmlFor="theme-select">Theme:</label> <select id="theme-select" value={currentTheme} onChange={(e) => switchTheme(e.target.value)} > {availableThemes.map(theme => ( <option key={theme} value={theme}> {theme} </option> ))} </select> </div> ); }

Best Practices

1. Theme Design

  • Use semantic color names
  • Maintain consistent color scales
  • Consider accessibility requirements
  • Test themes in different contexts

2. Theme Organization

  • Use descriptive theme names
  • Version your themes
  • Document theme purposes
  • Organize themes by project or brand

3. Performance

  • Minimize theme complexity
  • Use efficient color generation
  • Cache theme calculations
  • Optimize theme switching

4. Maintenance

  • Regular theme validation
  • Update themes with brand changes
  • Test themes across components
  • Document theme dependencies

Troubleshooting

Common Issues

Theme not applying

  • Check theme registration
  • Verify theme structure
  • Ensure proper CSS variable usage
  • Test theme validation

Color inconsistencies

  • Validate color formats
  • Check color scale generation
  • Verify theme inheritance
  • Test color accessibility

Performance issues

  • Optimize theme complexity
  • Use efficient color generation
  • Implement proper caching
  • Monitor theme switching overhead
Last updated on