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
17 KiB
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
- Theme Overview
- Light Mode (Default)
- Dark Mode
- Custom Themes
- Theme Switching
- CSS Variable Customization
- Component Theming
- 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:
<!-- 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
: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
// 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
: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
/* 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 class="custom-theme">
<body>...</body>
</html>
// Switch to custom theme
document.documentElement.classList.add('custom-theme');
// Remove other themes
document.documentElement.classList.remove('dark', 'light');
Brand-Specific Theme Example
/* 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
/* 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
<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
<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
// 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
<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
/* Override for entire page */
:root {
--primary: #your-color;
--space-4: 1.25rem;
--duration-normal: 0.3s;
}
Override for Specific Components
/* 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
// 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
/* 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
/* 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
/* 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
/* 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
/* ✅ 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
// 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):
// 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
// 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
// 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
/* 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
# 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:
/* ✅ Works */
--primary: #your-color;
/* ❌ Doesn't work */
background: #your-color;
Issue: Dark mode colors don't match
Solution: Adjust tokens in dark mode CSS:
:root.dark {
--primary: #lighter-color; /* Use lighter shade in dark mode */
}
Issue: Contrast is too low
Solution: Check WCAG ratios and adjust colors:
// 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