Dark Mode Color System with color-mix()

Use color-mix() to derive both light and dark theme palettes from one brand token. Switch themes by changing the 'base' rather than maintaining two parallel scales.

Common patterns

Detailed Explanation

One Brand, Two Themes

A common pain point in design systems is maintaining two parallel palettes — one for light mode and one for dark mode. color-mix() lets you derive both from the same brand color by changing the base the mixer pulls toward:

:root {
  --brand: #2563eb;
  --bg: white;
  --fg: #0a0a0a;
}

[data-theme="dark"] {
  --bg: #0a0a0a;
  --fg: #fafafa;
}

/* Surfaces derived from --brand and --bg */
.surface-1 { background: color-mix(in oklch, var(--brand) 4%, var(--bg)); }
.surface-2 { background: color-mix(in oklch, var(--brand) 8%, var(--bg)); }
.surface-3 { background: color-mix(in oklch, var(--brand) 12%, var(--bg)); }

In light mode, --bg is white, so each surface is a faintly brand-tinted near-white. In dark mode, --bg is near-black, so the same formula produces brand-tinted near-blacks — automatically.

Pressed/hover states from --fg

.button:hover {
  background: color-mix(in oklch, var(--brand), var(--fg) 12%);
}

This darkens in light mode (foreground is dark) and lightens in dark mode (foreground is light). One rule, both themes.

Caveats

  • Always test the resulting contrast in both themes — chroma stays similar but lightness flips. Use the accessibility color checker to validate AA/AAA pairs.
  • Some hue families (yellows, cyans) need a slightly different mix percentage between themes to feel balanced; treat the percentages as starting points, not gospel.

Use Case

Cut your design tokens roughly in half. Useful for product teams that ship a single component library across multiple themes (light/dark/high-contrast) without doubling the maintenance burden.

Try It — CSS color-mix() Generator

Open full tool