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