Files
dss/docs/THEME_SYSTEM.md
Digital Production Factory 276ed71f31 Initial commit: Clean DSS implementation
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
2025-12-09 18:45:48 -03:00

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

  1. Theme Overview
  2. Light Mode (Default)
  3. Dark Mode
  4. Custom Themes
  5. Theme Switching
  6. CSS Variable Customization
  7. Component Theming
  8. 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