Skip to content
oklchCSScolor modelsweb designcolor theoryfrontend

OKLCH in CSS: The Future of Color on the Web

HB
Hue Blender
·5 min read

The Problem OKLCH Solves

For two decades, web developers have reached for hsl() when they wanted to think about color in human terms — hue, saturation, lightness. It feels intuitive, until you actually try to build a design system with it. Pick hsl(60 100% 50%) (a vivid yellow) and hsl(240 100% 50%) (a vivid blue). Both claim 50% lightness. Place them side by side and the yellow looks blinding while the blue looks almost black.

That mismatch is not a quirk; it is a fundamental flaw in how HSL was constructed. HSL is a mathematical reshuffling of RGB, not a perceptual model. Its "lightness" axis has no relationship to how the human eye perceives brightness. The moment you try to generate a tonal scale, build a contrast-aware theme, or interpolate between hues, HSL betrays you.

OKLCH is the fix. It is a CSS color function backed by a real perceptual model, and it is now supported in every major browser. If you are starting a new design system in 2026, you should be writing colors in OKLCH.

Where OKLCH Comes From

OKLCH stands for OK (from Björn Ottosson's Oklab color space) plus Lightness, Chroma, Hue. Ottosson published Oklab in December 2020 as a perceptually uniform color space tuned with modern color appearance data. The goal was simple: a model that predicts perceived lightness, chroma, and hue accurately, while remaining computationally trivial.

OKLCH is the cylindrical form of Oklab, the same way HSL is the cylindrical form of RGB. The W3C added oklch() to CSS Color Module Level 4, and browsers shipped it quickly: Chrome 111 (March 2023), Firefox 113 (May 2023), and Safari 15.4 (March 2022). By 2026 it is well past the threshold where fallbacks are necessary for the vast majority of audiences.

Anatomy of oklch()

oklch(L C H)
oklch(L C H / alpha)
  • L — Lightness. From 0 (black) to 1 (white). Perceptually uniform: a step from 0.5 to 0.6 looks like the same brightness change regardless of the hue.
  • C — Chroma. The amount of color, from 0 (neutral gray) to ~0.37 for typical sRGB colors. The ceiling is hue-dependent — yellows hold less chroma than reds before falling outside gamut.
  • H — Hue. Degrees from 0 to 360. Roughly: red ~20°, yellow ~90°, green ~140°, blue ~220°, purple ~320°.
  • alpha — Optional, after a slash.

So oklch(0.7 0.15 140) is a soft, mid-light green. Only OKLCH guarantees that swapping the hue to 20 (red) or 220 (blue) keeps the perceived lightness intact.

Why Designers Should Care: Perceptual Uniformity in Practice

  • Generate tonal scales by formula. Walk lightness from 0.95 to 0.15 in fixed steps. The result looks evenly spaced — the kind of thing you previously had to hand-tune for hours in HSL.
  • Maintain accessible contrast across themes. A fixed L gap produces a roughly predictable contrast ratio across hues. You stop being ambushed by yellow text that fails on white when the visually identical blue text passed.
  • Interpolate gradients without dead zones. OKLCH avoids the muddy gray midpoints you get when blending opposing hues in RGB or HSL.
  • Rotate hues without changing the mood. Change H, hold L and C, and you get a sibling color of the same visual weight — exactly what a brand system needs for dark-mode equivalents.

Building a Color Scale in CSS

:root {
  --brand-h: 250;     /* hue: indigo-ish */
  --brand-c: 0.18;    /* chroma */

  --brand-100: oklch(0.95 calc(var(--brand-c) * 0.3) var(--brand-h));
  --brand-300: oklch(0.80 calc(var(--brand-c) * 0.6) var(--brand-h));
  --brand-500: oklch(0.60 var(--brand-c)             var(--brand-h));
  --brand-700: oklch(0.40 calc(var(--brand-c) * 0.9) var(--brand-h));
  --brand-900: oklch(0.20 calc(var(--brand-c) * 0.6) var(--brand-h));
}

Chroma tapers near the extremes because pure white and pure black cannot hold color. Try the same recipe with HSL and you will spend an afternoon nudging numbers; in OKLCH it works on the first attempt. Prototype interactively in the OKLCH Picker, then pipe the values into your tokens.

Gamut, sRGB, and Display P3

Chroma above ~0.37 is commonly outside sRGB. On a wide-gamut Display P3 screen, the browser renders the saturated color directly. On sRGB, it gamut-maps to the closest in-gamut color. The pragmatic pattern:

.cta {
  background: #048c2c;                    /* sRGB fallback */
}
@media (color-gamut: p3) {
  .cta { background: oklch(0.62 0.23 146); } /* punchier on P3 */
}

Most projects do not need this two-tier setup — a single OKLCH declaration with a sensible chroma works everywhere.

Tailwind CSS v4 Already Speaks OKLCH

If you use Tailwind CSS v4, you are already shipping OKLCH. The v4 release rebuilt the entire default palette in OKLCH to take advantage of the wider gamut:

--color-red-50:  oklch(0.971 0.013 17.38);
--color-red-100: oklch(0.936 0.032 17.717);
--color-red-200: oklch(0.885 0.062 18.334);

Every color family has eleven shades (50–950) with perceptually even steps. That is why bg-blue-500 and bg-yellow-500 finally feel like siblings. If you extend the Tailwind v4 palette with custom brand colors, define them in OKLCH too — mixing hex tokens with OKLCH defaults recreates the mismatch the rewrite was meant to fix.

Migration Notes

  • Do not translate hex to OKLCH literally. Pick a base hue and chroma, then build the scale fresh using perceptual properties.
  • Audit contrast pairs after switching. WCAG ratios are computed in sRGB. Our Contrast Checker accepts any CSS color format so you can verify without converting back to hex.
  • Prefer numbers over percentages for L. 0.7 and 70% are equivalent, but consistent notation makes tokens easier to scan.
  • Watch chroma per hue. Yellows and greens cap out at lower chroma than reds. If a color looks dull, you may have asked for more saturation than the gamut can deliver.

Key Takeaways

  • OKLCH is perceptually uniform — equal L values look equally bright across all hues.
  • Syntax: oklch(L C H) with L 0–1, C 0–~0.4, H 0–360.
  • Universal browser support since 2023: Chrome 111+, Firefox 113+, Safari 15.4+.
  • Tailwind v4 uses OKLCH internally — extend your palette in the same space.
  • Browsers gamut-map for older displays automatically; one declaration handles most cases.

Open the OKLCH Picker and spend ten minutes moving the L slider with the hue locked — that single experiment will change how you think about color tokens.

Try it yourself

Mix any colors with our Kubelka-Munk pigment simulation tool and get instant HEX, RGB, CMYK codes.

Open Mixer

Related Articles

Related Tools