color theory

Gamut Clamping

Gamut clamping is what browsers do when an OKLCH color exceeds the sRGB boundary. The engine projects the color to the nearest displayable value inside the gamut which usually rotates the hue even though you never changed the H coordinate. Your carefully chosen L value stays mostly intact but the perceived color shifts in ways that destroy the uniformity OKLCH promised. This is not theory. It is the runtime tax every team pays on their first wide gamut palette if they copy values straight from a P3 monitor into their CSS variables. Since OKLCH landed in browsers in 2022 this exact failure mode has wrecked more design system launches than any other OKLCH gotcha.

Gamut clamping is not a clean desaturation. The projection follows the shortest path to the sRGB blob in OKLCH space and that path swings hue by ten to thirty degrees depending on the angle and lightness. It is not consistent. Chrome 128 on a 2020 Dell clamps one way while Safari 18 on a 2024 MacBook clamps another because their gamut mapping algorithms differ. It is not visible inside Figma. Figma renders in wide gamut by default so the vivid blue you approve looks completely different once it hits a standard Windows laptop. It is not the same as chroma clipping. Clipping is the discipline you apply while building ramps. Clamping is the browser fixing your mistake after you ship. Conflate the two and you will chase hue bugs for weeks.

Look at the exact blue from the OKLCH migration article. The safe token oklch(0.65 0.18 260) sits inside sRGB on every device. Push the chroma to 0.28 for more pop and you cross the boundary. Desktop Chrome clamps it to roughly oklch(0.64 0.19 249). The hue rotated fourteen degrees toward violet. If that blue powers your brand links and primary buttons the entire visual hierarchy collapses. Stripe hit this in their 2023 design system refresh. Their success greens at oklch(0.62 0.25 145) clamped to a yellow green on enterprise Windows machines. The positive states started reading as cautionary. The team rebuilt every primitive with capped chroma per hue then layered the vivid P3 versions behind a media query. They shipped zero hue complaints after that.

Linear ran the same experiment in 2024 with their sidebar accents. The high chroma cobalt looked electric in the Figma file. Once deployed the clamped version sat too close to their link blue and users could not tell interactive states apart. Their contrast ratios also dropped below APCA thresholds in the clamped state. The rollback cost two full engineering days. Radix UI avoids this entirely by publishing scales that never exceed the sRGB chroma ceiling for each hue and lightness step. Copy their table instead of dragging sliders until the oklch.com warning triangle appears. The Tailwind v4 palette released in 2024 follows the same rule so millions of sites inherited safe values without realizing it.

Gradients expose the problem even faster. Set a background that moves from oklch(0.75 0.22 200) to oklch(0.75 0.22 300). In P3 it sails through clean cyan. In sRGB the interpolation path leaves the gamut midway and the browser clamps the center to a muddy gray with a brown tint. Users report it as a rendering bug. The same warping happens in hover transitions and state animations. One endpoint clamps while the other does not and the entire motion path buckles. Every one of these failures traces back to treating the OKLCH cylinder as if it were empty instead of remembering the sRGB potato sitting inside it.

Use gamut clamping only as a progressive enhancement after your safe sRGB foundation is locked. Build every primitive scale with chroma values proven to stay inside the gamut across all eleven lightness stops. Blues tolerate 0.22 at mid lightness. Yellows die at 0.14. Reds handle 0.25 before they clip. Once those numbers are set test the entire ramp on a consumer Windows machine running Chrome. Only then add the vivid overrides. The CSS pattern is two lines. First declare the safe token that never clamps. Then override it inside the media query. Root variables stay consistent for every user while MacBook and iPhone viewers get the extra saturation they paid for.

Do not use high chroma values in your base layer. Do not pick the brightest swatch your studio monitor can show and paste it into Tailwind config or CSS variables. Do not skip the post migration contrast audit because clamped colors change effective luminance. Do not apply the same aggressive chroma to dark mode tokens where the eye sees hue shifts faster. Do not trust tooling gaps in Figma. Define tokens in CSS first then pull them into Tokens Studio so the clamped reality is visible before anyone approves the system. Ignore any of these rules and you will ship palettes that look correct only on your own machine.

Gamut clamping turns your mathematically perfect OKLCH palette into an inconsistent mess the moment it leaves your machine so build your foundation inside the sRGB boundary and treat wide gamut color as the bonus it was meant to be.

Related terms

Keep exploring