OKLCH: the colour space designers are switching to
22 Sep 2026 · Colour · 7 min read
Every few years, the web gets a new way to describe colour, and most of them sink without a trace. OKLCH is different — it's being adopted with unusual speed by design systems, CSS authors and browser vendors alike. The reason is simple: it fixes the two things that have quietly annoyed everyone about HSL for twenty years, and it does so in a syntax you can start using in your stylesheets today.
This article explains what OKLCH is in plain words — no colour science degree required — what exactly it fixes about HSL, where it genuinely matters in day-to-day work, and where its limits still lie. The goal isn't to make you a colour scientist; it's to give you an intuition solid enough that reaching for OKLCH starts feeling obvious.
The problem it solves: HSL lies about lightness
HSL's lightness dial claims that 50% lightness is 50% lightness regardless of hue. Your eyes disagree, strongly. hsl(60, 100%, 50%) — pure yellow — looks blazingly bright, while hsl(240, 100%, 50%) — pure blue — looks distinctly dark, yet both insist they're at exactly half lightness. The discrepancy isn't subtle; it's the difference between a colour you'd put body text on and one you wouldn't dare.
This happens because HSL is a simple geometric transformation of RGB, and RGB was designed around the physics of phosphors and LEDs, not around human vision. The human eye is far more sensitive to green-yellow light than to blue — an evolutionary quirk of daylight vision — so equal RGB energy in different hues produces wildly unequal perceived brightness. HSL inherits this blindness wholesale. Every time you've tweaked an HSL lightness value and been surprised by the result, or built a "consistent" palette where the blues felt heavier than the yellows, you were tripping over this exact flaw.
OKLCH's first promise is that its lightness axis — the L — is perceptually uniform: equal steps in L look like equal steps in brightness, whatever the hue. oklch(70% 0.15 180) and oklch(70% 0.15 300) genuinely look equally bright. That one property unlocks most of what follows.
Reading OKLCH: lightness, chroma, hue
OKLCH has the same three-dial shape as HSL, which is deliberate — it was designed to feel familiar. L is lightness from 0 (black) to 1 (white), usually written as a percentage. C is chroma — roughly "how colourful", from 0 (grey) upward, with vivid colours landing around 0.15–0.3 depending on hue. H is hue as an angle in degrees, like HSL's, though the angles don't line up exactly with HSL's wheel.
So oklch(65% 0.18 160) reads as: fairly light, quite colourful, hue at 160° (a green-teal). Want it darker for a hover state? Drop L to 50% and nothing else — and unlike HSL, the result will look like the same colour darkened, not a different colour that happens to share a hue angle. Want it muted? Lower C. The dials do what their names say, which sounds like faint praise until you've fought HSL's lightness dial for an afternoon.
One practical note: chroma has no fixed maximum — the ceiling depends on the hue and lightness, because some hues simply can't be as vivid as others within what screens can display. Ask for oklch(90% 0.3 250) (very light, very colourful blue) and you've described a colour no screen can show; browsers clip it to the nearest displayable colour. This is normal and mostly harmless, but it's why OKLCH pickers often show a little "out of gamut" warning — the space is bigger than the screen.
Where it actually matters: palettes and gradients
The killer application is palette generation. The classic technique — take a hue, generate lightness steps — produces visibly uneven ramps in HSL: the yellow steps look washed out, the blue steps look muddy, and the "same" step across hues looks like different amounts of change. In OKLCH, stepping L from 20% to 95% at fixed C and H gives a ramp where each step looks like the same size as the last, for every hue. Design systems built on OKLCH ramps look more consistent with less fiddling, because the space is doing the perceptual work that designers used to do by eye, hue by hue.
The second killer application is gradients. Remember the grey dead zone — the muddy middle when blending two vivid colours in sRGB? Interpolating in OKLCH instead (linear-gradient(in oklch, #ff0000, #0000ff)) keeps chroma alive through the blend, because the interpolation path travels through perceptual space rather than through the sRGB cube's grey interior. Red-to-blue stays vivid all the way across. It's the single most visible quality upgrade you can make to a gradient, it's one keyword, and it works in all modern browsers.
How to use it in CSS today
- Direct values: color: oklch(65% 0.18 160); works in all current browsers. No fallback needed for modern audiences; for older browsers, declare an sRGB fallback first and let the cascade do its job.
- Relative colours: oklch(from #2DD4BF l calc(c - 0.05) h) takes an existing colour and modifies one channel — the cleanest way to build hover states and ramps programmatically.
- Gradient interpolation: linear-gradient(in oklch, ...) or in oklab for the rectangular variant. Use it whenever a gradient's middle looks muddy.
- Mixing: color-mix(in oklch, #2DD4BF 70%, white) blends in perceptual space — tints and shades that stay true to the hue.
You don't need to convert your whole codebase. A pragmatic adoption path: use OKLCH for new palettes and gradients, use relative-colour syntax for programmatic variants, and leave existing hex values alone until they need touching. The two notations coexist peacefully in the same stylesheet.
| Task | In HSL | In OKLCH |
|---|---|---|
| Darken a colour | Lower L — hue may appear to shift | Lower L — hue stays put perceptually |
| Build a lightness ramp | Steps look uneven across hues | Steps look even for every hue |
| Blend two vivid colours | Muddy grey middle (sRGB) | Vivid throughout with in oklch |
| Mute a colour | Lower S — lightness shifts too | Lower C — lightness genuinely held |
| Compare brightness of hues | Unreliable (yellow vs blue problem) | Compare L values directly |
The honest limitations
OKLCH is better, not perfect. First, "perceptually uniform" is an approximation — human vision is gloriously non-uniform, and OKLCH is a model fitted to experimental data, not a law of nature. The famous Hunt effect (colours look more vivid at higher brightness) and plain individual variation mean two people can still disagree about whether two OKLCH steps look equal. It's a much better approximation than HSL, not a solved problem.
Second, the tooling is still catching up. Most design tools' colour pickers remain HSL/HSB-first, which means OKLCH values often get authored in the browser or a dedicated converter and pasted back — a workable but clunky loop. And while browser support for the CSS syntax is now universal in current versions, anything touching older clients needs sRGB fallbacks, which doubles the declarations for a transition period.
Third, OKLCH doesn't fix judgement. A perceptually uniform space makes systematic palettes easier, but it can't tell you which hue fits a brand, whether a combination has enough contrast, or whether your red-green pairing survives colour-blindness. Those remain design questions, answered by testing rather than by notation. OKLCH is a better ruler — you still have to decide what to measure.
Still, better rulers matter. The move from HSL to OKLCH is one of those rare upgrades that's simultaneously more correct and more pleasant to use — the dials finally do what they say. If you take one thing from this article, make it the gradient keyword: in oklch is five characters that visibly improve nearly every vivid gradient you'll ever write.