Token Theme Switching
Token Theme Switching
Token theme switching enables applications to change their visual appearance by swapping between different token sets. The most common application is light/dark mode switching, but theme switching can also support brand variations, seasonal themes, or user customization. Effective theme switching happens seamlessly without jarring transitions or lost state.
What Is Token Theme Switching
Token theme switching is the mechanism by which an application transitions from one set of token values to another at runtime. This involves changing which token definitions are active, updating rendered styles, and maintaining consistency during the transition.
Theme switching operates on the semantic token layer, where the same token names map to different primitive values. Components reference semantic tokens without awareness of which theme is active, and the switching mechanism handles value resolution.
How Token Theme Switching Works
Web-based theme switching commonly uses CSS custom properties with scoped selectors.
Token generation produces theme-specific scopes:
:root {
--color-background: #ffffff;
--color-text: #171717;
}
[data-theme="dark"] {
--color-background: #171717;
--color-text: #f5f5f5;
}
JavaScript triggers theme changes:
function setTheme(theme) {
document.documentElement.dataset.theme = theme;
localStorage.setItem('theme', theme);
}
CSS cascade automatically applies the appropriate values when the attribute changes.
For system preference integration:
function getPreferredTheme() {
const stored = localStorage.getItem('theme');
if (stored) return stored;
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
}
// Listen for system preference changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
if (!localStorage.getItem('theme')) {
setTheme(e.matches ? 'dark' : 'light');
}
});
Key Considerations
- Initial theme should be determined before render to avoid flash
- System preferences should be respected unless user explicitly overrides
- Theme preference should persist across sessions
- Transitions can smooth theme changes but add complexity
- Server rendering requires coordination for initial theme
- Images and media may need theme-aware variants
- Third-party components may not support theming
- Accessibility must be maintained across all themes
Common Questions
How should flash of incorrect theme be prevented?
Flash of incorrect theme (FOIT) occurs when the page renders with one theme before switching to another, creating a jarring visual experience.
Blocking script approach applies theme before render:
<head>
<script>
const theme = localStorage.getItem('theme') ||
(matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.dataset.theme = theme;
</script>
</head>
This synchronous script blocks rendering briefly but ensures correct initial theme.
CSS-only approach uses media queries for initial state:
:root {
--color-background: #ffffff;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--color-background: #171717;
}
}
[data-theme="dark"] {
--color-background: #171717;
}
This applies system preference by default, with explicit theme selection overriding.
Server rendering can include the theme attribute in initial HTML when theme preference is known (via cookies or session).
How should theme transitions be handled?
Transitions can smooth the visual change when themes switch, preventing abrupt color shifts.
CSS transitions on token properties:
:root {
--color-background: #ffffff;
--color-text: #171717;
}
* {
transition: background-color 0.2s ease, color 0.2s ease;
}
This creates smooth color transitions but applies to all elements, which may be undesirable.
Selective transitions apply only during theme changes:
function setTheme(theme) {
document.documentElement.classList.add('theme-transition');
document.documentElement.dataset.theme = theme;
setTimeout(() => {
document.documentElement.classList.remove('theme-transition');
}, 200);
}
.theme-transition * {
transition: background-color 0.2s ease, color 0.2s ease;
}
No transitions may be preferable for performance or reduced motion preferences:
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
}
}
How should multiple themes beyond light/dark be handled?
Theme switching extends to any number of themes: brand themes, seasonal themes, or user-customizable themes.
Multiple theme attributes:
<html data-theme="dark" data-brand="secondary">
[data-theme="dark"] {
--color-background: #171717;
}
[data-brand="secondary"] {
--color-primary: #10B981;
}
[data-theme="dark"][data-brand="secondary"] {
/* Specific overrides for this combination */
}
Theme composition allows layered overrides.
Dynamic theme loading fetches theme CSS when needed:
async function loadTheme(themeName) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = `/themes/${themeName}.css`;
document.head.appendChild(link);
return new Promise(resolve => {
link.onload = resolve;
});
}
This approach supports unlimited themes without loading all theme CSS upfront.
Summary
Token theme switching enables runtime transitions between different token value sets. Web implementations typically use CSS custom properties with scoped selectors, switching via JavaScript-controlled attributes. Preventing flash of incorrect theme requires early theme determination. Transitions can smooth changes but should respect reduced motion preferences. Multiple themes extend the same pattern through additional attributes or dynamically loaded stylesheets.
Buoy scans your codebase for design system inconsistencies before they ship
Detect Design Drift Free