Design System Problems

Token Theme Switching

January 15, 2026 • 5 min read

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

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
← Back to Token Management