Gradients Without the Gray Dead Zone
Blue-to-red in sRGB muds through gray. Interpolate in OKLCH instead.
You needed a hero gradient from brand blue to brand coral. In the legacy linear-gradient syntax the middle turned dingy brown-gray. The designer asked if you exported the wrong file. You had not. You had faithfully interpolated between two sRGB hex values, and the math did exactly what sRGB math does: it drove straight through the center of the color cube, where vivid hues lose chroma and become mud.
This is the gray dead zone, a predictable failure mode of hue-opposed gradients in RGB and HSL. It is not a browser bug, not a Figma export glitch, not a monitor calibration issue. It is the geometry of the color space you chose for interpolation. CSS Color Module Level 4 fixes it by letting you declare which color space the browser uses when it walks from stop A to stop B. OKLCH, the cylindrical form of Björn Ottosson’s Oklab, keeps gradients vivid because it interpolates along perceptual lightness, chroma, and hue axes instead of red-green-blue channels.
Adam Argyle and the Chrome team documented the syntax as Color 4 shipped. MDN’s gradient comparison examples show the same blue-to-red stop pair in sRGB, HSL, OKLCH, and other spaces side by side. Evil Martians recommend OKLCH as the default interpolation space for UI gradients because it aligns with how modern tokens are authored.
This article explains the hue path problem, OKLCH interpolation syntax, fallbacks, and a production story where the gradient was correct in staging and wrong in production for a boring reason.
What interpolation actually does
A CSS gradient is not a blend of two pictures. It is a series of color samples along a path. For each pixel position the browser asks: given start color, end color, and distance along the path, what color belongs here? That question is underspecified until you name a color space.
In the pre-Color 4 world, browsers interpolated in sRGB. sRGB is a triangle inside the gamut of human-visible colors. Two vivid corners of that triangle, saturated blue and saturated red, sit far apart in hue but both near the edge of vividness. The straight line between them in RGB space cuts through the interior of the triangle, where colors are less saturated. The midpoint is not a vivid purple. It is a desaturated brownish gray.
HSL does not rescue you. HSL gradients rotate hue while holding saturation and lightness nominally steady, but HSL’s axes are themselves distorted. A blue-to-red HSL gradient may avoid the exact sRGB mud pit and fall into a different one, often murky magenta or flat cyan-purple depending on stops. The fix is not picking prettier endpoints. The fix is changing the path.
Think of hue as an angle on a wheel. Blue might sit near two hundred fifty degrees. Orange-red might sit near twenty-five degrees. Interpolating hue is a question of which arc you travel, short through purples or long through greens and yellows. In sRGB interpolation you are not explicitly choosing either arc. RGB channels blend independently, which produces desaturation instead of a clean hue journey.
OKLCH makes the path explicit. Lightness, chroma, and hue are separate axes. Interpolating in OKLCH walks lightness from L₁ to L₂, chroma from C₁ to C₂, and hue along the shortest arc by default, or the longest arc if you specify longer hue. Because Oklab was designed so perceived color difference correlates with Euclidean distance, the midpoint of a blue-to-orange gradient in OKLCH lands on a vivid magenta or violet, not on gray.
Linear, radial, and conic syntax in OKLCH
The Color 4 syntax inserts the color space immediately after the gradient function name. Direction and interpolation space are independent knobs. Store endpoints as tokens so marketing can retune colors without touching gradient syntax.
:root {
--gradient-start: oklch(0.55 0.18 264);
--gradient-end: oklch(0.65 0.16 35);
}
.hero {
background: linear-gradient(
in oklch to right,
var(--gradient-start),
var(--gradient-end)
);
}
Compare with the legacy form between the same brand hex values:
.hero-legacy {
background: linear-gradient(to right, #2563eb, #f97316);
}
Open both in a Color 4-capable browser. The legacy gradient’s center is gray-brown. The OKLCH gradient’s center is saturated purple-pink. Same brand intent, different path. MDN’s Color 4 gradient documentation demonstrates the same stop pair across spaces; the visual difference is immediate and reproducible enough to end arguments about whether the designer exported the wrong asset.
Multi-stop brand gradients keep each segment vivid when every leg interpolates in OKLCH. Radial and conic gradients accept the same in oklch modifier. A green glow from bright center to darker edge stays in the green hue family without passing through gray at the midpoint ring. Conic gradients expose the short-arc versus long-arc question visibly. longer hue tells the browser to take the long path around the hue circle for an even spectrum at fixed lightness and chroma. MDN documents longer hue alongside shorter hue, increasing hue, and decreasing hue for fine control in data visualization and loading spinners.
When the pipeline stripped in oklch silently
A media company rebuilt its marketing site with a hero gradient between brand blue and brand coral. Design approved OKLCH stops in Storybook. Staging matched the Figma mockup. Production looked like 2014 mud. Engineering assumed CDN caching until a developer inspected computed styles on production and saw only sRGB interpolation.
The build pipeline used a PostCSS plugin chain written when in oklch was not yet in the team’s lint vocabulary. A minification step rewrote background declarations and dropped the color space token as an unrecognized fragment. Another step flattened custom properties to hex for an obsolete performance experiment. The browser never received in oklch. It received two hex stops and did exactly what sRGB does.
The fix was not redesigning the gradient. It was fixing the pipeline contract. Gradients with color-space interpolation were excluded from the aggressive optimizer. Custom properties for gradient stops were marked do-not-flatten. CI added a test that computed background-image on the hero fixture contained oklch after full production build, not only after dev server. Fallback remained sRGB first for older browsers, OKLCH inside @supports.
Design had approved the gradient in a Figma frame exported as PNG for stakeholder review, which masked the CSS interpolation issue entirely. Stakeholders signed off on a raster preview while engineering implemented vector CSS. The lesson was to add a living style guide page that rendered the actual CSS gradient, not only a static export, before executive approval. After the pipeline fix, that page became a required link in design review tickets touching hero backgrounds.
The team documented that gradient quality is a build artifact property, not only a CSS authoring choice. Authors can write perfect OKLCH gradients and ship mud if preprocessing strips the space. DevTools gradient editor helped A/B sRGB and OKLCH on the same element during the incident postmortem.
Debugging order for gradients that still disappoint after pipeline fixes: endpoint chroma too low produces polite gray blends regardless of space. Gamut clipping may dull extreme stops before interpolation begins per CSS Color 4 gamut mapping. Forgotten in oklch from typo or preprocessor puts you back in sRGB silently. Mixed formats work but verify computed values. Semi-transparent overlays above the gradient reintroduce mud visually even when the gradient itself is fine.
Fallbacks, palette alignment, and when to use other spaces
Not every user agent parses in oklch. Ship a safe default first, then enhance inside @supports. The sRGB gradient is the fallback, not the other way around. Users on older browsers see mud. Users on modern browsers see vivid color. That trade is acceptable for many marketing heroes. For product UI where gradient quality is core to brand, widen support checks or provide raster fallbacks.
Combine with color-gamut media queries when Display P3 hardware should preserve extra chroma along the path. OKLCH gradients already benefit on sRGB. P3 can render more vivid midpoints before clipping.
Gradients fail quietly when endpoints come from mismatched spaces. If brand blue was tuned as HSL and brand coral as OKLCH from different tools, stops do not share a perceptual coordinate system before interpolation begins. Standardize endpoints as OKLCH palette tokens first, then reference those tokens in gradients. Mud disappears at both endpoint and path layers.
.hero {
background: linear-gradient(to right, #2563eb, #f97316);
}
@supports (background: linear-gradient(in oklch, #fff, #000)) {
.hero {
background: linear-gradient(
in oklch to right,
oklch(0.55 0.18 264),
oklch(0.65 0.16 35)
);
}
}
OKLCH is the right default for UI gradients between brand hues. Photographic overlays adjacent to images may prefer srgb linear for luminance relationships. Subtle neutral washes between near-grays matter less because chroma is near zero. Print-matched assets may need hand-tuned stops per medium regardless of space.
Color 4 supports multiple interpolation spaces beyond OKLCH. Lab, LCH, Oklab, HSL, HWB, and XYZ remain available for specialized workflows. For UI gradients between brand hues, the meaningful comparison is usually default sRGB versus OKLCH. Oklab and OKLCH describe the same perceptual space in rectangular and cylindrical coordinates. CSS authors prefer OKLCH because hue angles match design vocabulary. Image processing pipelines sometimes prefer Oklab for historical tooling reasons. Web UI should standardize on OKLCH unless a measured reason exists to diverge.
Custom properties in gradient stops enable theme switches without rewriting gradient declarations. When --gradient-start and --gradient-end change under dark mode, the same linear-gradient(in oklch …) rule produces a darker journey appropriate to charcoal surfaces. Verify endpoints in both themes because a light-mode coral endpoint may need a different chroma on dark backgrounds to avoid halation even when interpolation space is correct.
Eased pacing with extra stops does not change interpolation space per segment. Each leg between stops still walks through OKLCH if you declare in oklch once on the gradient function. Position hints at zero, forty, and one hundred percent only alter where samples occur along the line, not how each sample is computed. Multi-stop sunsets and data-viz fills benefit from three or more OKLCH stops when brand guidelines specify intermediate brand colors that must appear at exact positions.
Relationship to wide-gamut displays is complementary. OKLCH interpolation fixes the gray dead zone on sRGB hardware. Display P3 hardware may render higher chroma midpoints before gamut mapping compresses them. Test gradients on both device classes when brand endpoints use high chroma. A vivid OKLCH midpoint that looks correct on a MacBook may clip differently on an external sRGB monitor attached to the same machine.
Loading shimmer and skeleton placeholders often use subtle gradients between two near-neutral OKLCH coordinates. Even low-chroma gradients benefit from in oklch when hue drifts would be visible on large surfaces. A shimmer that crosses hue slightly in sRGB can look dirty on white cards. Keeping chroma near zero does not eliminate the hue path problem entirely because sRGB neutral grays are not neutral in perception when interpolated against a tinted surface token.
Animation and reduced-motion preferences interact with gradients in ways interpolation space does not solve but can worsen. A pulsing hero gradient that shifts stop positions every frame turns small interpolation errors into visible flicker. Prefer solid tokens or static gradients for motion-heavy backgrounds unless performance testing on low-end hardware approves the effect. OKLCH fixes mud. It does not fix GPU cost from animated multi-stop backgrounds on every page load.
Design handoff should include gradient endpoints in the same OKLCH coordinates production uses, not only a PNG preview. Figma stores sRGB hex internally, but export pipelines can convert stops to OKLCH before engineers write CSS. When design and engineering share endpoint numbers, arguments about mud shift from export quality to intentional chroma choices both sides can edit in the token file.
Conic gradients for data visualization and loading indicators benefit from explicit longer hue or shorter hue keywords when arc direction is part of the UX contract. A progress ring that should warm from blue to green through yellow needs the long arc. A subtle background wash between two nearby hues should use default shorter arc behavior. Document arc choice in component specs because automatic hue interpolation is correct mathematically but surprising visually when designers expected the other path around the wheel.
Reduced transparency overlays on OKLCH gradients preserve vivid paths underneath better than sRGB overlays when the overlay itself is a semi-transparent neutral. If marketing adds a white scrim at forty percent opacity above a vivid OKLCH hero, verify the combined stack in DevTools compositing view, not only the gradient in isolation.
Brand refreshes that change only one gradient stop still require re-checking the midpoint path. OKLCH interpolation means the center color is a function of both endpoints. Changing coral without revisiting blue can shift the perceived mud zone even when in oklch is declared correctly. Treat gradient pairs as coupled tokens in the map, not independent swatches, when both stops appear in the same hero component.
For the hero banner, pricing card highlight, chart area fill, and loading shimmer from blue to coral, green to teal, purple to pink, in oklch is the one-line fix that addresses a decade of gray dead zones. Declare the space. Keep vivid endpoints. Verify the production pipeline preserves both. Let Oklab’s perceptual geometry do the walking.