Theming¶
The design system supports light and dark themes through CSS custom properties.
Theme Structure¶
Themes work by remapping semantic tokens to mode-specific values:
Base Token (e.g., --bg-light)
↓
Theme Mapping (in light.css or dark.css)
↓
Semantic Token (e.g., --bg)
↓
Used in Components
Light Theme¶
Defined in src/themes/light.css:
:root:not(.dark) {
--bg: var(--bg-light); /* #dde3e9 */
--bg-elevated: #f5f7fa;
--text-primary: var(--text-primary-light);
--text-secondary: rgba(13, 41, 53, 0.8);
--shadow: var(--shadow-light);
--neu-1: var(--neu-light-1);
--neu-2: var(--neu-light-2);
--neu-inset: var(--neu-light-inset);
--border: var(--border-light);
--primary-text: #186891;
--success-text: #024d47;
/* ... */
}
Dark Theme¶
Defined in src/themes/dark.css:
.dark {
--bg: var(--bg-dark); /* #0f1419 */
--bg-elevated: var(--bg-dark-elevated);
--text-primary: var(--text-primary-dark);
--text-secondary: var(--text-secondary-dark);
--shadow: var(--shadow-dark);
--neu-1: var(--neu-dark-1);
--neu-2: var(--neu-dark-2);
--neu-inset: var(--neu-dark-inset);
--border: var(--border-dark);
--primary-text: #55ccd9;
--success-text: #03a89e;
/* ... */
}
Enabling Dark Mode¶
Toggle the .dark class on the <html> element. In this project, dark mode is managed via component state in the admin-ui TopBar.
Manual Toggle¶
function ThemeToggle() {
const [isDark, setIsDark] = useState(false);
const toggle = () => {
document.documentElement.classList.toggle('dark');
setIsDark(!isDark);
};
return (
<button onClick={toggle}>
Toggle Theme
</button>
);
}
System Preference¶
/* Respect OS preference as default */
@media (prefers-color-scheme: dark) {
:root {
/* Apply dark token mappings */
}
}
Using Theme-Aware Tokens¶
Always use semantic tokens instead of hardcoded values:
/* ✅ Good - Theme aware */
.my-component {
background: var(--bg);
color: var(--text-primary);
box-shadow: var(--neu-1);
border-color: var(--border);
}
/* ❌ Bad - Not theme aware */
.my-component {
background: #dde3e9;
color: #0d2935;
}
Token Reference¶
Backgrounds¶
| Token | Light | Dark |
|---|---|---|
--bg |
#dde3e9 |
#0f1419 |
--bg-elevated |
#f5f7fa |
#1a1f26 |
Text¶
| Token | Light | Dark |
|---|---|---|
--text-primary |
#0d2935 |
#e8edf2 |
--text-secondary |
rgba(13,41,53,0.8) |
rgba(232,237,242,0.8) |
--primary-text |
#186891 |
#55ccd9 |
--success-text |
#024d47 |
#03a89e |
--warning-text |
#f2c744 |
#f2c744 |
--error-text |
#b85500 |
#f28d35 |
Shadows¶
| Token | Light | Dark |
|---|---|---|
--shadow |
Soft light shadow | Subtle dark shadow |
--neu-1 |
Light neumorphic | Dark neumorphic |
--neu-2 |
Elevated light | Elevated dark |
--neu-inset |
Light inset | Dark inset |
Borders¶
| Token | Light | Dark |
|---|---|---|
--border |
rgba(13,41,53,0.15) |
rgba(85,204,217,0.2) |
--border-subtle |
rgba(13,41,53,0.08) |
rgba(85,204,217,0.12) |
Customizing Themes¶
To customize themes, override the CSS custom properties:
/* Custom light theme overrides */
:root:not(.dark) {
--primary-accent: #your-brand-color;
--primary-text: #your-text-color;
}
/* Custom dark theme overrides */
.dark {
--primary-accent: #your-brand-color-dark;
--primary-text: #your-text-color-dark;
}
Creating a New Theme¶
- Create a new CSS file (e.g.,
themes/custom.css) - Define all semantic token mappings
- Import in your app after the base styles
/* themes/custom.css */
.theme-custom {
--bg: #your-bg;
--bg-elevated: #your-elevated-bg;
--text-primary: #your-text;
--primary-accent: #your-accent;
/* Define all tokens... */
}
WCAG Compliance¶
The design system uses WCAG-compliant text colors for accessibility:
- Light mode text colors have sufficient contrast against light backgrounds
- Dark mode text colors have sufficient contrast against dark backgrounds
- Interactive elements meet minimum contrast requirements
Color contrast ratios: - Normal text: 4.5:1 minimum - Large text: 3:1 minimum - UI components: 3:1 minimum
Testing Themes¶
Use browser dev tools to test:
- Toggle
.darkclass on<html>element - Check contrast ratios in Accessibility panel
- Test with system preference simulation