Chapters

Part 1 — Foundations · Chapter 5

Perceptual spaces: from Lab to OKLCH

Lab promised in 1976 that equal numbers would mean equal-looking steps — and mostly delivered, except in blue. What "perceptually uniform" actually buys, how OKLab fixed Lab's famous failure, and why OKLCH's three knobs earn the names HSL faked.

Chapter 4 ended with a demolition notice: HSL's knobs are geometry wearing perception's name tags, and the engine must never do math in them. This chapter builds the replacement. But before meeting it, be precise about what's actually being promised — because "perceptual color space" gets thrown around like a brand name, and it's a measurable claim.

distance 70 — near twins
rgb(0 200 0)rgb(70 200 0)
ΔE2000 = 2.5
distance 70 — different colors
rgb(255 150 0)rgb(255 220 0)
ΔE2000 = 24.4
Two pairs, and each pair is exactly the same distance apart in sRGB — one channel moved by 70. The green pair reads as one color; the orange pair crosses a category boundary. Measured with ΔE2000, the industry's perceived-difference formula, the second gap is ten times the first. Equal numbers, unequal difference: sRGB distance measures the cube, not you.

Both pairs are exactly 70 apart in sRGB coordinates. Your eye scores one gap at "same color" and the other at "different colors" — ΔE2000, the industry's formula for perceived difference, says the second gap is ten times the first. That's the disease. Perceptually uniform is the cure, and it's one sentence: equal numeric distance means equal perceived difference — anywhere in the space, in any direction. Move a color by 0.05, and it should look changed by the same amount whether it's a light blue or a dark orange, whether you nudged its lightness or its hue.

No coordinate remap of the RGB cube can deliver that — chapter 4 was the proof. Delivering it takes what HSL never had: measurements of people. Sit observers down, show them colors, record what they report as equally light, equally different, the same hue. Then build a space whose geometry matches the reports.

Lab: the 1976 attempt

That's exactly what the CIE — the international standards body for light and color — did, publishing CIELAB in 1976. Its lightness axis L* is fitted to human judgments, runs 0 (black) to 100 (diffuse white), and puts mid gray at 50 by design.

You already trust L*. It's the "lightness" meter under every playground since chapter 3 — the scale that scored your halfway-gray guess, the scale that caught HSL claiming L 50% for both a near-white yellow and a dark blue. Every one of those meters was CIELAB all along. On lightness, Lab earns its reputation, and it earned its five decades as the workhorse of color measurement.

Around the hue circle, it cracks — and the crack has an address. CSS Color 4 lists Lab's known problems, and the first is hue linearity in the blue region: hold Lab's hue coordinate fixed and reduce a saturated blue's chroma, and the spec says it plainly — the color "becomes noticeably purple."

CIELAB — hue pinned at 296°
OKLab — hue pinned at 264°
CIELABmeasured hue293° (+29°)
OKLabmeasured hue264°
Each space fades the same blue toward white along its own straight line — its hue coordinate never moves. The readout is the measured hue: CIELAB's "constant hue" slides from 264° into the 290s — visibly purple — while OKLab's stays put. This is the spec's own diagnosis: "as a saturated blue has its Chroma progressively reduced, it becomes noticeably purple."

Lab swears that whole top strip is one hue — its hue coordinate is pinned at 296° the entire way. Measured against how people actually see hue, it slides 33° toward purple. The strip is the famous failure you've met in the wild: every tool that ever faded a blue and got lavender, every "lighter blue" token that came out lilac, was doing faithful math in Lab-shaped coordinates.

Blue is the headline, but the spec lists more: hues elsewhere are "not perfect," and Lab over-predicts chroma differences for saturated colors — a chroma step that's obvious near gray is barely visible out at the vivid edge. The color-science fix was ΔE2000, a patched distance formula that corrects for Lab's warps at measurement time, including a rotation term specifically for the blue region. It works — it's the referee in this chapter's first demo — but by the spec's own admission it "does not help with hue curvature," and gradients, ramps, and pickers live in the coordinates themselves. When plain distance needs a formula that elaborate stacked on top, the geometry is confessing.

OKLab: optimized to fix exactly that

In December 2020, Björn Ottosson — a game developer, not a committee — published "A perceptual color space for image processing." His diagnosis of Lab (and its sibling Luv) matched the spec's: their "largest issue is their inability to predict hue. In particular blue hues are predicted badly."

His fix was pragmatic. Take the skeleton of IPT — a 1998 space that models hue direction well: convert to the eye's three cone responses, apply a compressive nonlinearity, mix down to three axes. Then numerically optimize the matrices and the exponent against perceptual data — lightness and chroma fitted to the predictions of CAM16 (the CIE's current heavyweight appearance model), hue fitted to the same measured constant-hue data IPT was built on. The exponent came out a whisker from 1/3, and OKLab locks it there: a cube root, like Lab's. OKLab is not new physics or new experiments — it's a careful curve-fit that inherits the best measured behavior of the models before it, in a form cheap enough to run on every pixel.

Why not use CAM16 itself, if it has the best data? Because it's built to describe appearance, not to be worked in — it wants viewing conditions as input and isn't safe to do arithmetic in. OKLab trades a sliver of fidelity for coordinates that are cheap, stable, and workable. The name is honest: not perfect — okay.

The result graduated from blog post to standard: CSS Color 4 adopted it with the assessment that OKLab has "improved hue linearity, hue uniformity, and chroma uniformity compared to CIE LCH." Uniform enough, in fact, that its difference formula needs no patches: ΔEOK is plain straight-line distance, the thing Lab needed ΔE2000 to fake.

Three spaces now claim they can walk from a color to white in even steps: sRGB by stepping its channels, CIELAB and OKLab by stepping their measured coordinates. Time to judge them with the only instrument that matters.

PlaygroundThree ramps, equal numeric steps each — which one does your eye believe?
Eight equal steps from one seed to white, taken in three different spaces. Click the ramp whose steps look even and whose color stays itself.

No labels until you commit — your eye is the instrument here.

That lineup is worth slowing down for, because uniformity is really two promises, and the instruments test them separately. The gray twins test pacing — are the steps evenly sized? The hue strip tests steering — does the walk stay on course? On red and green seeds, CIELAB passes both, and even raw sRGB is only mildly off — bunched dark steps, under ten degrees of drift; chapter 3 told you why value-ladder math is roughly perceptual. Give CIELAB a blue, though, and something stranger happens: its pacing stays metronome-perfect while its steering fails — perfectly even steps marching confidently into purple. Pacing and steering are separate promises, a gray-ramp test only checks the first, and OKLab is the only row that keeps both on every seed. Not magic — it's simply the only one of the three that was fitted to hue data.

OKLCH: the same space, with the knobs you wanted

OKLab's native axes are Lab-shaped: L plus two opponent coordinates, a (green–red) and b (blue–yellow) — great for math, clumsy for humans. Wrap those two into polar coordinates and you get OKLCH: the identical space, re-gripped as Lightness, Chroma (distance from the gray axis), Hue (angle around it). Same cylinder HSL sold you in 1978 — but this time each knob is a measurement:

oklch(0.65 0.100 250) — #5e93caits light, as gray
L — lightness0.65
C — chroma0.100
H — hue250°
light emitted
27.6%
Spin H through a full turn: the color changes completely, its gray twin barely moves. Drain C to zero: it fades to gray without getting lighter or darker. Only L moves the twin. The light meter does wiggle a few points as H turns — perceived lightness is not a light meter (chapter 3) — and C's ceiling shifts as you move: that's your screen running out of colors, which is chapter 6's problem, not OKLCH's.
  • L predicts lightness across hues. 0 is black, 1 is white, and two colors sharing an L look equally light whatever their hue — the guarantee chapter 4's strips showed HSL faking and OKLCH keeping. One scale note: OKLab's L is its own axis, not L*/100 — chapter 3's halfway gray rgb(119 119 119) sits at L* 50 but OKLab L 0.57.
  • C is real vividness. Zero is exactly gray, and equal C looks comparably vivid across hues — no more "100% saturated" pastels. It's an open-ended axis, not a percentage: visible colors run to about 0.5 by the spec's reckoning, and sRGB tops out around 0.32, at magenta.
  • H moves hue and nothing else. Turn it and lightness and vividness hold still — the exact torture test HSL failed in chapter 4.

The knobs are independent because the space is uniform: each axis moves one perceptual quantity. That's the entire reason a color engine can exist — "20% lighter," "same vividness, different hue," "half the chroma" become single-axis arithmetic that lands where perception expects.

One porting warning before you start turning them. OKLCH's hue wheel is not HSL's wheel rotated by a constant — the two are offset and unevenly stretched:

hsl(H 100% 50%)
oklch(0.70 0.10 H)
0°60°120°180°240°300°
Same six angles, two wheels. HSL's 0° is red; OKLCH's 0° is pink-magenta, and red sits near 29°. HSL's 240° is pure blue; that same blue reads 264° in OKLCH. Neither wheel is wrong — they're rotated and unevenly stretched relative to each other. Port a palette by converting the colors, never by copying the angles.

What OKLab doesn't promise

Honest tools admit their calibration range. OKLab is a perceptual color space, not a color appearance model: it maps coordinates to colors under one implied viewing situation — D65 white, well-lit screen, and (per the CSS spec) "adaptation to the color being defined." It has no inputs for surround, ambient light, or state of adaptation, where models like CAM16 do. Chapter 1's lesson therefore survives every space in this course:

Both chips are oklch(0.65 0.1 250) — identical to the last bit. Chapter 1's effect passes straight through: OKLab's coordinates describe the color, not the company it keeps. No surround, no adaptation, no viewing conditions — by design.

Two smaller soft spots, filed honestly. Very saturated colors can look brighter than a gray of the same measured luminance — the Helmholtz–Kohlrausch effect — and OKLab's L, like Lab's before it, largely doesn't model it, so treat L as least trustworthy on vivid accents. And the dark end runs loose: OKLab's L disagrees with L* near black, enough that Ottosson later published a corrected lightness (Lr) for his color pickers. For the engine this matters less than it sounds — chapter 11 draws lightness curves rather than stepping L evenly, and the curve absorbs it — but it's a known soft spot, not a rounding error.

File all three caveats the same way: OKLCH tells you what a color is; it doesn't tell you what a color does in context. The first job belongs to the space. The second is yours, and it's most of Part 2.

The decision this unlocks

Chapter 4 banned HSL from the engine's math; this chapter seats the replacement: OKLCH is the engine's working space. Colors may arrive as hex, HSL, anything parseable — they're converted at the door, and every generation and adjustment step runs on L, C, and H.

The payoff lands in Part 2's core chapters: a color ramp will be designed as three curves — lightness along the ramp (chapter 11), chroma along the ramp (chapter 12), hue along the ramp (chapter 13). When those chapters plot curves, the axes of the graph paper are the knobs you just turned — and drawing a ramp as three independent curves is only a sane idea because the axes are honest and independent. The same property makes state derivation (chapter 18) finally safe: a −0.05 L nudge for hover is the same visible nudge on the blue button and the yellow one. That was the exact operation chapter 4 banned in HSL.

Two doors this deliberately leaves closed: OKLCH will happily name colors your screen can't show — you felt the C slider's ceiling move; that edge is chapter 6 — and knowing where colors live doesn't yet say how to travel between them, which is chapter 7's subject.

Before you move on

Further reading