Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
723 lines
17 KiB
Markdown
723 lines
17 KiB
Markdown
# Design System Theme System Guide
|
|
|
|
**Version:** 1.0.0
|
|
**Last Updated:** December 7, 2025
|
|
|
|
Complete guide to theming and customizing the design system.
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Theme Overview](#theme-overview)
|
|
2. [Light Mode (Default)](#light-mode-default)
|
|
3. [Dark Mode](#dark-mode)
|
|
4. [Custom Themes](#custom-themes)
|
|
5. [Theme Switching](#theme-switching)
|
|
6. [CSS Variable Customization](#css-variable-customization)
|
|
7. [Component Theming](#component-theming)
|
|
8. [Best Practices](#best-practices)
|
|
|
|
---
|
|
|
|
## Theme Overview
|
|
|
|
The design system includes:
|
|
- ✅ Light mode (default)
|
|
- ✅ Dark mode (CSS class-based)
|
|
- ✅ Full customization via CSS variables
|
|
- ✅ Per-component theme support
|
|
- ✅ Automatic contrast checking
|
|
|
|
### How Theming Works
|
|
|
|
Themes are implemented using CSS custom properties (variables) that automatically apply based on a class on the root element:
|
|
|
|
```html
|
|
<!-- Light mode (default) -->
|
|
<html>
|
|
<body>...</body>
|
|
</html>
|
|
|
|
<!-- Dark mode -->
|
|
<html class="dark">
|
|
<body>...</body>
|
|
</html>
|
|
```
|
|
|
|
---
|
|
|
|
## Light Mode (Default)
|
|
|
|
Light mode is the default theme with clean, bright colors optimized for daytime viewing.
|
|
|
|
### Color Palette (Light Mode)
|
|
|
|
| Token | Color | HEX | Usage |
|
|
|-------|-------|-----|-------|
|
|
| `--primary` | Blue | `#3b82f6` | Primary actions, focus |
|
|
| `--secondary` | Indigo | `#6366f1` | Secondary elements |
|
|
| `--success` | Green | `#10b981` | Success feedback |
|
|
| `--warning` | Amber | `#f59e0b` | Warning messages |
|
|
| `--destructive` | Red | `#ef4444` | Error/delete actions |
|
|
| `--info` | Cyan | `#0ea5e9` | Information |
|
|
| `--foreground` | Slate-900 | `#0f172a` | Primary text |
|
|
| `--muted-foreground` | Slate-600 | `#64748b` | Secondary text |
|
|
| `--background` | White | `#ffffff` | Page background |
|
|
| `--card` | Slate-50 | `#f8fafc` | Card backgrounds |
|
|
| `--card-foreground` | Slate-900 | `#1e293b` | Card text |
|
|
| `--border` | Slate-200 | `#e2e8f0` | Borders |
|
|
| `--input` | White | `#ffffff` | Input backgrounds |
|
|
| `--ring` | Blue | `#3b82f6` | Focus rings |
|
|
|
|
### Light Mode Appearance
|
|
|
|
- Clear distinction between elements
|
|
- High contrast text (4.5:1+)
|
|
- Subtle shadows for depth
|
|
- Neutral backgrounds
|
|
- Vibrant accent colors
|
|
|
|
### CSS
|
|
|
|
```css
|
|
:root {
|
|
/* Light mode variables are set by default */
|
|
--primary: #3b82f6;
|
|
--secondary: #6366f1;
|
|
--success: #10b981;
|
|
--warning: #f59e0b;
|
|
--destructive: #ef4444;
|
|
--info: #0ea5e9;
|
|
--foreground: #0f172a;
|
|
--muted-foreground: #64748b;
|
|
--background: #ffffff;
|
|
--card: #f8fafc;
|
|
--card-foreground: #1e293b;
|
|
--border: #e2e8f0;
|
|
--input: #ffffff;
|
|
--ring: #3b82f6;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Dark Mode
|
|
|
|
Dark mode provides eye-friendly colors for low-light environments.
|
|
|
|
### Enabling Dark Mode
|
|
|
|
```javascript
|
|
// Add dark class to enable dark mode
|
|
document.documentElement.classList.add('dark');
|
|
```
|
|
|
|
### Color Palette (Dark Mode)
|
|
|
|
| Token | Color | HEX | Usage |
|
|
|-------|-------|-----|-------|
|
|
| `--primary` | Light Blue | `#60a5fa` | Primary actions |
|
|
| `--secondary` | Light Indigo | `#818cf8` | Secondary elements |
|
|
| `--success` | Light Green | `#34d399` | Success feedback |
|
|
| `--warning` | Light Amber | `#fbbf24` | Warning messages |
|
|
| `--destructive` | Light Red | `#f87171` | Error/delete actions |
|
|
| `--info` | Light Cyan | `#38bdf8` | Information |
|
|
| `--foreground` | Slate-50 | `#f1f5f9` | Primary text |
|
|
| `--muted-foreground` | Slate-400 | `#cbd5e1` | Secondary text |
|
|
| `--background` | Slate-950 | `#0f172a` | Page background |
|
|
| `--card` | Slate-900 | `#1e293b` | Card backgrounds |
|
|
| `--card-foreground` | Slate-50 | `#e2e8f0` | Card text |
|
|
| `--border` | Slate-800 | `#334155` | Borders |
|
|
| `--input` | Slate-900 | `#1f2937` | Input backgrounds |
|
|
| `--ring` | Light Blue | `#60a5fa` | Focus rings |
|
|
|
|
### Dark Mode Appearance
|
|
|
|
- Dark backgrounds reduce eye strain
|
|
- Brighter text for readability
|
|
- Adjusted accent colors for clarity
|
|
- Softer shadows
|
|
- Maintained contrast ratios
|
|
|
|
### CSS
|
|
|
|
```css
|
|
:root.dark {
|
|
--primary: #60a5fa;
|
|
--secondary: #818cf8;
|
|
--success: #34d399;
|
|
--warning: #fbbf24;
|
|
--destructive: #f87171;
|
|
--info: #38bdf8;
|
|
--foreground: #f1f5f9;
|
|
--muted-foreground: #cbd5e1;
|
|
--background: #0f172a;
|
|
--card: #1e293b;
|
|
--card-foreground: #e2e8f0;
|
|
--border: #334155;
|
|
--input: #1f2937;
|
|
--ring: #60a5fa;
|
|
}
|
|
```
|
|
|
|
### Component Support
|
|
|
|
All 9 components support dark mode:
|
|
|
|
- ✅ DsButton - All variants themed
|
|
- ✅ DsInput - Form fields themed
|
|
- ✅ DsCard - Background and text adjusted
|
|
- ✅ DsBadge - Color variants adjusted
|
|
- ✅ DsToast - Backgrounds and text adjusted
|
|
- ✅ DsWorkflow - Step colors adjusted
|
|
- ✅ DsNotificationCenter - Scrollbar styled
|
|
- ✅ DsActionBar - Background themed
|
|
- ✅ DsToastProvider - Inherits from toasts
|
|
|
|
---
|
|
|
|
## Custom Themes
|
|
|
|
Create custom themes by overriding CSS variables.
|
|
|
|
### Creating a Custom Theme
|
|
|
|
```css
|
|
/* custom-theme.css */
|
|
:root.custom-theme {
|
|
/* Custom brand colors */
|
|
--primary: #6d28d9; /* Deep purple */
|
|
--secondary: #d946ef; /* Magenta */
|
|
--success: #22c55e; /* Bright green */
|
|
--warning: #eab308; /* Bright yellow */
|
|
--destructive: #dc2626; /* Bright red */
|
|
|
|
/* Custom backgrounds */
|
|
--background: #f9f5ff; /* Lavender white */
|
|
--card: #f3e8ff; /* Light purple */
|
|
--card-foreground: #3f0f63; /* Dark purple text */
|
|
|
|
/* Custom text colors */
|
|
--foreground: #1f1337; /* Dark purple */
|
|
--muted-foreground: #6b5280; /* Muted purple */
|
|
|
|
/* Adjust other tokens as needed */
|
|
--border: #e9d5ff; /* Light purple border */
|
|
--input: #fafaf9; /* Warm white */
|
|
}
|
|
```
|
|
|
|
### Applying Custom Theme
|
|
|
|
```html
|
|
<html class="custom-theme">
|
|
<body>...</body>
|
|
</html>
|
|
```
|
|
|
|
```javascript
|
|
// Switch to custom theme
|
|
document.documentElement.classList.add('custom-theme');
|
|
|
|
// Remove other themes
|
|
document.documentElement.classList.remove('dark', 'light');
|
|
```
|
|
|
|
### Brand-Specific Theme Example
|
|
|
|
```css
|
|
/* tech-brand-theme.css - Modern tech company theme */
|
|
:root.tech-brand {
|
|
--primary: #0066ff; /* Vibrant blue */
|
|
--secondary: #00d4ff; /* Cyan */
|
|
--success: #00ff88; /* Neon green */
|
|
--warning: #ffaa00; /* Bright orange */
|
|
--destructive: #ff3366; /* Hot pink */
|
|
|
|
--foreground: #1a1a2e; /* Dark blue */
|
|
--background: #0f1419; /* Almost black */
|
|
--card: #161b22; /* Slightly lighter black */
|
|
--card-foreground: #ffffff; /* Pure white text */
|
|
--border: #30363d; /* Dark gray borders */
|
|
}
|
|
|
|
:root.tech-brand.dark {
|
|
/* Dark mode would be even more dark/vibrant */
|
|
--primary: #00d4ff; /* Swap primary/secondary in dark */
|
|
--secondary: #0066ff;
|
|
--success: #00ff88;
|
|
}
|
|
```
|
|
|
|
### Corporate Theme Example
|
|
|
|
```css
|
|
/* corporate-theme.css - Professional corporate theme */
|
|
:root.corporate {
|
|
--primary: #003366; /* Navy blue */
|
|
--secondary: #666666; /* Gray */
|
|
--success: #006600; /* Dark green */
|
|
--warning: #cc6600; /* Brown-orange */
|
|
--destructive: #990000; /* Dark red */
|
|
|
|
--foreground: #000000; /* Pure black */
|
|
--background: #ffffff; /* Pure white */
|
|
--card: #f5f5f5; /* Light gray */
|
|
--card-foreground: #333333; /* Dark gray text */
|
|
--border: #cccccc; /* Medium gray borders */
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Theme Switching
|
|
|
|
### Simple Theme Toggle
|
|
|
|
```html
|
|
<button id="theme-toggle">Toggle Dark Mode</button>
|
|
|
|
<script>
|
|
const button = document.getElementById('theme-toggle');
|
|
|
|
button.addEventListener('click', () => {
|
|
document.documentElement.classList.toggle('dark');
|
|
});
|
|
</script>
|
|
```
|
|
|
|
### Theme Switcher with Dropdown
|
|
|
|
```html
|
|
<select id="theme-selector">
|
|
<option value="">Light</option>
|
|
<option value="dark">Dark</option>
|
|
<option value="custom-theme">Custom Brand</option>
|
|
<option value="corporate">Corporate</option>
|
|
</select>
|
|
|
|
<script>
|
|
const selector = document.getElementById('theme-selector');
|
|
|
|
selector.addEventListener('change', (e) => {
|
|
const theme = e.target.value;
|
|
|
|
// Remove all theme classes
|
|
document.documentElement.classList.remove('dark', 'custom-theme', 'corporate');
|
|
|
|
// Add selected theme
|
|
if (theme) {
|
|
document.documentElement.classList.add(theme);
|
|
}
|
|
|
|
// Save preference
|
|
localStorage.setItem('preferred-theme', theme);
|
|
});
|
|
|
|
// Load saved preference
|
|
const saved = localStorage.getItem('preferred-theme');
|
|
if (saved) {
|
|
document.documentElement.classList.add(saved);
|
|
selector.value = saved;
|
|
}
|
|
</script>
|
|
```
|
|
|
|
### System Preference Detection
|
|
|
|
```javascript
|
|
// Detect system dark mode preference
|
|
function getSystemTheme() {
|
|
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
return 'dark';
|
|
}
|
|
return 'light';
|
|
}
|
|
|
|
// Apply system theme on page load
|
|
function applySystemTheme() {
|
|
const saved = localStorage.getItem('preferred-theme');
|
|
const theme = saved || getSystemTheme();
|
|
|
|
if (theme === 'dark') {
|
|
document.documentElement.classList.add('dark');
|
|
} else {
|
|
document.documentElement.classList.remove('dark');
|
|
}
|
|
}
|
|
|
|
applySystemTheme();
|
|
|
|
// Listen for system theme changes
|
|
window.matchMedia('(prefers-color-scheme: dark)')
|
|
.addEventListener('change', (e) => {
|
|
if (!localStorage.getItem('preferred-theme')) {
|
|
// Only change if user hasn't set preference
|
|
if (e.matches) {
|
|
document.documentElement.classList.add('dark');
|
|
} else {
|
|
document.documentElement.classList.remove('dark');
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
### Theme Switcher Component
|
|
|
|
```html
|
|
<div class="theme-switcher">
|
|
<button id="light-theme" aria-label="Light theme">☀️</button>
|
|
<button id="dark-theme" aria-label="Dark theme">🌙</button>
|
|
<button id="auto-theme" aria-label="Auto theme">🔄</button>
|
|
</div>
|
|
|
|
<script>
|
|
const lightBtn = document.getElementById('light-theme');
|
|
const darkBtn = document.getElementById('dark-theme');
|
|
const autoBtn = document.getElementById('auto-theme');
|
|
|
|
lightBtn.addEventListener('click', () => {
|
|
document.documentElement.classList.remove('dark');
|
|
localStorage.setItem('preferred-theme', 'light');
|
|
});
|
|
|
|
darkBtn.addEventListener('click', () => {
|
|
document.documentElement.classList.add('dark');
|
|
localStorage.setItem('preferred-theme', 'dark');
|
|
});
|
|
|
|
autoBtn.addEventListener('click', () => {
|
|
localStorage.removeItem('preferred-theme');
|
|
applySystemTheme();
|
|
});
|
|
|
|
// Highlight current theme
|
|
function updateButtons() {
|
|
const isDark = document.documentElement.classList.contains('dark');
|
|
lightBtn.classList.toggle('active', !isDark);
|
|
darkBtn.classList.toggle('active', isDark);
|
|
}
|
|
|
|
updateButtons();
|
|
document.addEventListener('theme-change', updateButtons);
|
|
</script>
|
|
```
|
|
|
|
---
|
|
|
|
## CSS Variable Customization
|
|
|
|
### Override Global Variables
|
|
|
|
```css
|
|
/* Override for entire page */
|
|
:root {
|
|
--primary: #your-color;
|
|
--space-4: 1.25rem;
|
|
--duration-normal: 0.3s;
|
|
}
|
|
```
|
|
|
|
### Override for Specific Components
|
|
|
|
```css
|
|
/* Custom styling for one component */
|
|
.my-special-button {
|
|
--primary: #ff6b6b; /* Use different color */
|
|
background: var(--primary);
|
|
}
|
|
|
|
/* Custom styling for theme */
|
|
:root.my-theme {
|
|
--primary: #667eea;
|
|
--secondary: #764ba2;
|
|
}
|
|
```
|
|
|
|
### Using Variables in JavaScript
|
|
|
|
```javascript
|
|
// Get variable value
|
|
const primaryColor = getComputedStyle(document.documentElement)
|
|
.getPropertyValue('--primary')
|
|
.trim();
|
|
|
|
// Set variable value
|
|
document.documentElement.style.setProperty('--primary', '#ff0000');
|
|
|
|
// In component
|
|
class CustomComponent extends HTMLElement {
|
|
getPrimaryColor() {
|
|
return getComputedStyle(this).getPropertyValue('--primary').trim();
|
|
}
|
|
|
|
setPrimaryColor(color) {
|
|
this.style.setProperty('--primary', color);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Component Theming
|
|
|
|
### Button Theming
|
|
|
|
```css
|
|
/* Light mode button colors */
|
|
.ds-btn[data-variant="primary"] {
|
|
background: var(--primary);
|
|
color: white;
|
|
}
|
|
|
|
.ds-btn[data-variant="success"] {
|
|
background: var(--success);
|
|
color: white;
|
|
}
|
|
|
|
/* Dark mode adjustments (automatic via tokens) */
|
|
:root.dark .ds-btn[data-variant="primary"] {
|
|
background: var(--primary); /* Already adjusted in tokens */
|
|
color: white;
|
|
}
|
|
```
|
|
|
|
### Input Theming
|
|
|
|
```css
|
|
/* Light mode input */
|
|
.ds-input {
|
|
background: var(--input);
|
|
border: 2px solid var(--border);
|
|
color: var(--foreground);
|
|
}
|
|
|
|
/* Dark mode input (automatic) */
|
|
:root.dark .ds-input {
|
|
background: var(--input); /* Dark background */
|
|
border-color: var(--border); /* Lighter border */
|
|
color: var(--foreground); /* Light text */
|
|
}
|
|
|
|
/* Focus state (works in both themes) */
|
|
.ds-input:focus {
|
|
border-color: var(--ring);
|
|
box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 20%, transparent);
|
|
}
|
|
```
|
|
|
|
### Card Theming
|
|
|
|
```css
|
|
/* Light mode card */
|
|
.ds-card {
|
|
background: var(--card);
|
|
color: var(--card-foreground);
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
/* Dark mode card (automatic) */
|
|
:root.dark .ds-card {
|
|
background: var(--card); /* Dark background */
|
|
color: var(--card-foreground); /* Light text */
|
|
border-color: var(--border); /* Darker border */
|
|
}
|
|
```
|
|
|
|
### Toast Theming
|
|
|
|
```css
|
|
/* Light mode toast */
|
|
.ds-toast[data-type="success"] {
|
|
border-left: 4px solid var(--success);
|
|
background: linear-gradient(135deg,
|
|
color-mix(in oklch, var(--success) 5%, var(--card)) 0%,
|
|
var(--card) 100%);
|
|
}
|
|
|
|
/* Dark mode toast (automatic) */
|
|
:root.dark .ds-toast[data-type="success"] {
|
|
/* Uses dark theme tokens automatically */
|
|
border-left-color: var(--success);
|
|
background: linear-gradient(135deg,
|
|
color-mix(in oklch, var(--success) 5%, var(--card)) 0%,
|
|
var(--card) 100%);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
### 1. Always Use Tokens
|
|
|
|
```css
|
|
/* ✅ Good */
|
|
.my-button {
|
|
background: var(--primary);
|
|
color: var(--foreground);
|
|
padding: var(--space-4);
|
|
}
|
|
|
|
/* ❌ Bad */
|
|
.my-button {
|
|
background: #3b82f6;
|
|
color: #0f172a;
|
|
padding: 16px;
|
|
}
|
|
```
|
|
|
|
### 2. Test in Both Themes
|
|
|
|
```javascript
|
|
// Test dark mode
|
|
document.documentElement.classList.add('dark');
|
|
// Check colors, contrast, visibility
|
|
|
|
// Test light mode
|
|
document.documentElement.classList.remove('dark');
|
|
// Check colors, contrast, visibility
|
|
```
|
|
|
|
### 3. Maintain Contrast
|
|
|
|
Ensure all text meets WCAG 2.1 AA minimum (4.5:1 contrast ratio):
|
|
|
|
```javascript
|
|
// Validate contrast
|
|
function checkContrast(foreground, background) {
|
|
const fgLum = getRelativeLuminance(foreground);
|
|
const bgLum = getRelativeLuminance(background);
|
|
const ratio = (Math.max(fgLum, bgLum) + 0.05) / (Math.min(fgLum, bgLum) + 0.05);
|
|
return ratio >= 4.5; // WCAG AA
|
|
}
|
|
```
|
|
|
|
### 4. Respect User Preferences
|
|
|
|
```javascript
|
|
// Always check prefers-color-scheme
|
|
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
// Apply dark mode
|
|
} else {
|
|
// Apply light mode
|
|
}
|
|
|
|
// Listen for changes
|
|
window.matchMedia('(prefers-color-scheme: dark)')
|
|
.addEventListener('change', (e) => {
|
|
// Update theme
|
|
});
|
|
```
|
|
|
|
### 5. Announce Theme Changes
|
|
|
|
```javascript
|
|
// Emit custom event when theme changes
|
|
const event = new CustomEvent('theme-change', {
|
|
detail: { theme: isDark ? 'dark' : 'light' }
|
|
});
|
|
document.dispatchEvent(event);
|
|
|
|
// Listen for theme changes
|
|
document.addEventListener('theme-change', (e) => {
|
|
console.log('Theme changed to:', e.detail.theme);
|
|
});
|
|
```
|
|
|
|
### 6. Handle Transitions Smoothly
|
|
|
|
```css
|
|
/* Smooth color transitions when switching themes */
|
|
* {
|
|
transition: background-color 0.2s ease, color 0.2s ease;
|
|
}
|
|
|
|
/* But avoid transitions on initial load */
|
|
*:not(:root):not(body) {
|
|
transition: none;
|
|
}
|
|
|
|
:root.theme-changing * {
|
|
transition: background-color 0.2s ease, color 0.2s ease;
|
|
}
|
|
```
|
|
|
|
### 7. Document Custom Themes
|
|
|
|
```markdown
|
|
# Custom Theme: TechBrand
|
|
|
|
## Colors
|
|
- Primary: #0066ff (Vibrant Blue)
|
|
- Secondary: #00d4ff (Cyan)
|
|
- Success: #00ff88 (Neon Green)
|
|
|
|
## Usage
|
|
```html
|
|
<html class="tech-brand">
|
|
<!-- Content -->
|
|
</html>
|
|
```
|
|
|
|
## Dark Mode
|
|
Automatically supports dark theme with enhanced contrast.
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Issue: Theme not applying
|
|
**Solution:** Make sure CSS is loaded before HTML:
|
|
```javascript
|
|
// Load in this order:
|
|
import '@company/design-system/css/variants.css';
|
|
import '@company/design-system/css/components.css';
|
|
```
|
|
|
|
### Issue: Custom colors not working
|
|
**Solution:** Use CSS custom properties, not hardcoded values:
|
|
```css
|
|
/* ✅ Works */
|
|
--primary: #your-color;
|
|
|
|
/* ❌ Doesn't work */
|
|
background: #your-color;
|
|
```
|
|
|
|
### Issue: Dark mode colors don't match
|
|
**Solution:** Adjust tokens in dark mode CSS:
|
|
```css
|
|
:root.dark {
|
|
--primary: #lighter-color; /* Use lighter shade in dark mode */
|
|
}
|
|
```
|
|
|
|
### Issue: Contrast is too low
|
|
**Solution:** Check WCAG ratios and adjust colors:
|
|
```javascript
|
|
// Aim for 4.5:1 minimum (WCAG AA)
|
|
// 7:1+ for best accessibility (WCAG AAA)
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
The design system theming system provides:
|
|
|
|
✅ **Light & Dark modes** - Out of the box
|
|
✅ **42 CSS variables** - Full customization
|
|
✅ **Automatic component support** - All 9 components themed
|
|
✅ **High contrast** - WCAG 2.1 AA compliant
|
|
✅ **System preference detection** - Respects OS preference
|
|
✅ **Persistence** - Save user theme preference
|
|
✅ **Custom themes** - Create brand-specific themes
|
|
✅ **Smooth transitions** - Animate theme changes
|
|
|
|
---
|
|
|
|
**For more help:** design-system@company.com
|