ChromaWell

Blog

What Is OKLCH, and Why It Matters

OKLCH fixes a real defect in HSL and Lab — here's the actual math behind it, the CSS syntax, browser support reality, and when it's worth switching.

Every color model developers use daily — RGB, HSL, even the older CIE Lab — has a blind spot: none of them tracks human perceived brightness accurately across hues. OKLCH was built in 2020 by Björn Ottosson specifically to close that gap, and it's now shipping natively in every major browser's CSS engine. This post covers what it actually computes, why the older models fall short, and where it concretely changes the output you ship.

The problem OKLCH was built to solve

Take two colors at identical HSL lightness: hsl(240, 100%, 50%) (a saturated blue) and hsl(60, 100%, 50%) (a saturated yellow). Same "L" value, 50%. Look at them side by side and the yellow reads as dramatically brighter. That's not a rendering bug — HSL's lightness channel is defined as (max(R,G,B) + min(R,G,B)) / 2, a purely geometric average of the RGB cube, with no reference to the fact that green light registers as brighter to human vision than red or blue light of the same intensity. The WCAG relative-luminance formula gets this right — L = 0.2126R + 0.7152G + 0.0722B — because it's derived from actual photopic sensitivity data, weighting green far more heavily than blue. HSL ignores all of that.

CIE Lab (1976) was an earlier attempt at a perceptually-driven model, and it's a real improvement — it's what this site's color name finder uses for nearest-match distance, because Delta-E in Lab space correlates with perceived difference far better than raw RGB distance. But Lab has its own known issue: it isn't hue-linear. Moving along a constant "hue angle" in Lab's a*/b* plane doesn't actually hold perceived hue constant, which shows up as visible color shifts in Lab-interpolated gradients, especially through blues and purples.

What OKLCH actually computes

OKLCH is built on OKLab, which re-derives the perceptual color space using modern color-matching data and cone-response modeling, then converts to a cylindrical Lightness/Chroma/Hue form the same way HSL is a cylindrical reinterpretation of RGB. The conversion path from sRGB is: linearize the channel (undo gamma), transform into an LMS-like cone space via a fixed 3×3 matrix, cube-root each component, then apply a second 3×3 matrix to get L, a, and b. Chroma and hue fall out of a and b the same way they do in Lab — C = sqrt(a² + b²), H = atan2(b, a). Here's the actual matrix step, in the form this site's converter runs:

// linear sRGB -> LMS-ish space (Ottosson's matrices)
const l_ = 0.4122214708*rl + 0.5363325363*gl + 0.0514459929*bl;
const m_ = 0.2119034982*rl + 0.6806995451*gl + 0.1073969566*bl;
const s_ = 0.0883024619*rl + 0.2817188376*gl + 0.6299787005*bl;

// cube root, then to OKLab
const l3 = Math.cbrt(l_), m3 = Math.cbrt(m_), s3 = Math.cbrt(s_);
const L = 0.2104542553*l3 + 0.793617785*m3  - 0.0040720468*s3;
const a = 1.9779984951*l3 - 2.428592205*m3  + 0.4505937099*s3;
const b = 0.0259040371*l3 + 0.7827717662*m3 - 0.808675766*s3;

The practical result: equal steps in OKLCH's L channel look like genuinely equal steps in perceived brightness, across every hue, far more reliably than HSL's L or even Lab's L. Run any hex through this site's color converter to see its OKLCH value alongside RGB, HSL, and Lab side by side.

Where this changes real output

Tint/shade ramps. A design-token scale built by walking OKLCH lightness in even steps produces a visibly more even ramp than the same steps in HSL — the HSL version tends to have a muddy, oddly-dark step somewhere in the blues and an oddly-bright one in the yellows, because HSL's lightness doesn't track perception. The shades, tints & tones generator computes ramps this way. See building a design token color system for how this feeds into an actual token scale.

Gradients. linear-gradient(#ff0000, #0000ff) interpolates through RGB by default, and the RGB midpoint of red and blue is a desaturated gray-purple — not the vivid magenta most people expect. Modern CSS lets you specify the interpolation space directly:

.gradient-oklch {
  background: linear-gradient(in oklch, #ff0000, #0000ff);
}

This keeps chroma high through the transition instead of collapsing toward gray in the middle, because OKLCH's chroma and hue are carried as independent channels rather than emerging from a linear blend of R, G, and B. The gradient generator and gradient-to-code tools produce ready-to-paste CSS; the broader gradient technique picture is in gradient design trends.

Programmatic palette generation. If you're generating a triadic or analogous palette by rotating hue and want the resulting colors to look like they belong to the same "weight class," rotating hue in OKLCH while holding L and C constant does that far more reliably than the equivalent HSL rotation, because HSL's saturation and lightness both interact with hue in ways OKLCH's C and L don't. See color theory basics for developers and try it directly with the color harmonies tool.

The CSS syntax and current browser support

The oklch() function has been supported in Chrome, Firefox, and Safari since 2023, and is one of several new color functions that shipped together — lab(), lch(), color(), and color-mix() among them. Syntax:

:root {
  --brand: oklch(62% 0.19 259);      /* L (0-100%), C (0-~0.4), H (degrees) */
  --brand-alpha: oklch(62% 0.19 259 / 0.5);
}

One real gotcha: OKLCH's gamut is larger than sRGB, so it's possible to specify an OKLCH triple that has no valid sRGB equivalent — browsers gamut-map it to the nearest in-range color rather than erroring, which can produce a slightly different result than you'd expect if you're not aware the value was out of range to begin with. See understanding color gamuts for the P3/Rec.2020 context this becomes relevant in. For teams not ready to commit CSS source to oklch() syntax directly, converting at build time and keeping hex as the shipped value is a reasonable middle ground — the hex-to-RGB and RGB-to-hex tools round-trip the values either direction.

Should you switch?

If you're hand-picking a handful of brand colors, OKLCH's benefit is marginal — you're eyeballing the result anyway. It earns its keep the moment color is *generated* rather than picked: tint/shade ramps, interpolated gradients, and hue-rotated harmony sets all get measurably better with almost no added complexity, since the conversion is a fixed, well-documented set of matrix multiplications rather than a design judgment call. Compare it directly against the older models in hex vs. RGB vs. HSL, and see the broader model landscape in the complete guide to color.

Get new tools & color guides by email

Occasional emails when a new tool, dataset, or in-depth guide ships. No spam, unsubscribe anytime.