Skip to content
100% in your browser. Nothing you paste is uploaded — all processing runs locally. Read more →

Why your design system needs OKLCH (and what to actually use it for)

5 min read #color #oklch #design-systems #css

If you’ve ever tried to make a “blue 500” and a “green 500” that look like the same level of darkness, you’ve run into HSL’s biggest problem: its “lightness” axis lies. hsl(220 80% 50%) and hsl(120 80% 50%) have the same L value. They look nothing alike.

OKLCH fixes this. The same L in OKLCH actually gives you the same perceived darkness across hues. That’s the whole pitch, and it unlocks four things you couldn’t easily do before.

1. Tonal scales that are actually tonal

In Tailwind / Material / IBM Carbon, “blue 500” and “red 500” are picked by hand to look like the same darkness. That hand-tuning is a tax on every design system that wants more than the standard 11 steps.

In OKLCH, you generate them:

:root {
  --blue-50:  oklch(96% 0.04 260);
  --blue-100: oklch(92% 0.07 260);
  --blue-200: oklch(85% 0.12 260);
  --blue-300: oklch(75% 0.18 260);
  --blue-400: oklch(65% 0.20 260);
  --blue-500: oklch(55% 0.22 260);
  --blue-600: oklch(45% 0.20 260);
  --blue-700: oklch(35% 0.18 260);
  --blue-800: oklch(25% 0.14 260);
  --blue-900: oklch(15% 0.08 260);
}

Change 260 to 25 and you get a red scale where the lightness levels match. Change to 145 and you get a green scale that matches both. The human-perceived contrast between any two adjacent steps is consistent across hues.

2. Accessible text-on-color, predictably

WCAG contrast comes from luminance. OKLCH’s L is approximately perceptual lightness, which correlates better with luminance than HSL’s L does. In practice this means: L < 50% reliably needs white text; L > 65% reliably needs dark text. The 50–65% band is where you actually have to check.

Compare HSL: hsl(60 100% 50%) is yellow with L=50% but luminance is ~0.93 — definitely needs dark text. HSL’s lightness misled you.

This single rule lets you generate --text-on-blue-500, --text-on-red-500, etc. mechanically rather than picking each by hand.

3. Real animation between colors

CSS transitions on color interpolate in sRGB by default — the path between red and green passes through ugly grey-brown territory. OKLCH interpolation goes through a hue arc that stays vivid:

.button {
  background: oklch(55% 0.22 260);
  transition: background 200ms;
}
.button:hover {
  background: oklch(55% 0.22 25);
  transition: background 200ms linear;
  /* Browsers that support it: */
  transition-behavior: allow-discrete;
}

For named-color → named-color crossfades, OKLCH path looks dramatically better than sRGB. As of 2026 this is shipping in stable Chrome / Firefox / Safari behind the standard color interpolation hint:

.fade {
  --from: oklch(55% 0.22 260);
  --to: oklch(55% 0.22 25);
  background: color-mix(in oklch, var(--from), var(--to) 50%);
}

4. Sane high-contrast mode and dark themes

Generating a dark theme from a light theme is mostly “invert lightness.” In OKLCH:

:root[data-theme="dark"] {
  --bg:    oklch(15% 0.01 250);  /* was 98% */
  --fg:    oklch(95% 0.01 250);  /* was 15% */
  --accent: oklch(70% 0.18 260); /* was 55% */
}

That last line is the trick: when you flip light→dark, the accent has to get lighter, not darker, because it now sits on a dark background. OKLCH makes the L/C/H axes orthogonal enough that you can adjust just L without the hue shifting.

Try it the same way in HSL and you’ll find yourself hand-tuning every swatch — the perceived hue shifts as L changes.

What about wide-gamut?

OKLCH was designed for display-p3 and wider gamuts. Most browsers can display oklch values that are out-of-gamut for sRGB and clamp gracefully on sRGB displays. The practical takeaway: feel free to push C (chroma) beyond 0.30 — modern devices will show vivider colors, older ones see a clamped fallback.

Caveats

TL;DR

OKLCH gives you a perceptually-uniform color space directly in CSS. The practical wins for design systems:

  1. Generate tonal scales programmatically — same L across hues looks like same darkness.
  2. Predict text-on-color from L alone — <50% = white text, >65% = dark text.
  3. Animate colors smoothly through color-mix(in oklch, ...).
  4. Build a dark theme by inverting L instead of hand-tuning each swatch.

Plug any color into the color tool — it shows the OKLCH form alongside hex/RGB/HSL so you can start training your eye.