OKLCH Gamut Math, Visually: Why Some Colors "Do Not Exist"
OKLCH lets you describe colors that no screen can show. Understanding why - and how browsers map them back into reality - turns gradients and tokens from guesswork into intent.
Type oklch(0.7 0.4 25) into your stylesheet and the browser will not show what you asked for. The chroma value 0.4 lives outside the sRGB volume - and even outside Display P3 - at that hue and lightness. The browser quietly maps it to the nearest visible color. Understanding the mapping turns OKLCH from a black box into a tool you can wield deliberately.
The gamut, simply
OKLCH itself is unbounded - any combination of L, C and H is mathematically valid. Real screens are bounded. sRGB is a small box, P3 a larger box, Rec.2020 larger still. A color "in gamut" sits inside the box; "out of gamut" means the math is fine, but no display can render it.
The shape of the box
This is the critical insight: the in-gamut region for any hue is not a rectangle. It is a deformed teardrop. At extreme lightness (very dark or very bright), maximum chroma is small. At mid-lightness, chroma can be large. The shape changes per hue - reds and purples have a high-chroma midpoint, yellows and greens are squashed.
So oklch(0.5 0.3 30) (a red) is achievable in P3, but oklch(0.95 0.3 90) (a near-white yellow at high chroma) is impossible everywhere. Same chroma, very different feasibility.
What browsers do at the edge
CSS Color 4 specifies a gamut mapping algorithm: keep L and H, reduce C until you fit the destination gamut. This is the "perceptually closest" answer for that lightness and hue.
Older approaches (naive RGB clipping) shifted the hue while clipping. OKLCH's mapping preserves the hue - which is why interpolated gradients in OKLCH stay clean even when intermediate colors slip out of gamut.
Why this matters for designers
- Two screens, two answers. The same OKLCH spec can render slightly different on an sRGB and a P3 panel - because the gamut box is different. Use the Display P3 tool to see the in-gamut split for your tokens.
- Brand colors at the edge. Some brand reds are right on the sRGB boundary. A small chroma bump moves them out, and an older display will quietly desaturate them. Pick chroma values you know fit, or accept the soft fall-off.
- Gradients are not just two stops. The straight-line path between two stops in OKLCH may pass through out-of-gamut territory. Browsers map those midpoints back; the visible result is a slight chroma dip in the middle. To stay in gamut, lower both endpoints' chroma, or use intermediate stops that bend the path.
Reading the teardrop
For practical work, you do not need to memorize the shape. Two heuristics cover most cases:
- Highest chroma sits near L=0.55-0.70. This is the "vibrant zone".
- Yellows tap out earliest at high lightness. A bright yellow at L=0.92 cannot be very chromatic. A bright blue at L=0.92 has even less room.
A practical rule
For tokens that absolutely must render identically across sRGB and P3, cap chroma at 0.18 in OKLCH. Anything higher starts to differ between displays. For accent colors that are allowed to "pop" on better screens, go to 0.30 - you get the wide-gamut wow on P3 and a perceptually-mapped fallback on sRGB.
Visualize your palette's chroma distribution in our Spatial tool - it plots colors in OKLCH space so you can see at a glance which ones live near the gamut wall.