Workarounds for Buggy Gradients

CSS has a great new feature where you can specify the color space to use by adding in <colorspace> to the gradient (not yet supported in Firefox). This is especially useful for avoiding the gray dead zone that comes from rectangular color spaces like the default sRGB. Just look at the difference between a blue-to-yellow gradient in srgb compared to oklch with the same beginning and end:

linear-gradient(to right, blue, yellow)

srgb — blue to yellow

linear-gradient(to right in oklch, blue, yellow)

oklch — blue to yellow

I like that second one a lot better, though there certainly may be specific use cases where the first could make sense, too.

Unfortunately, if you’ve experimented with this much, you may have run into some weird behavior when building any gradients to white, black, gray, or transparent. This is especally bad when using blue, but it does affect other hues as well. Look at the teal and purple that shows up in these gradients, when they really should consist only of shades of blue:

oklch — blue to white
oklch — blue to gray
oklch — blue to black
oklch — blue to transparent

Here are red gradients, some of which show an orange or burgandy center.

oklch — red to white
oklch — red to gray
oklch — red to black
oklch — red to transparent

And to make matters worse, this behavior isn’t the same across browsers. The center of that second gradient (blue to gray) is teal in Chrome, but purple in Safari. The red to gray gradient differs between the browsers as well. Thankfully, there’s a reasonable workaround once you understand the problem.

The most reasonable solution is to do the gradient in sRGB or another rectangular color space. When you aren’t defining a gradient between different hues, the benefit of using a polar color space doesn’t really apply. However, out of academic curiousity, I dug into the cause of these bugs and happened to find some reasonable workaround within the OKLCH and HSL color spaces.

Workarounds in OKLCH

As far as I understand it, this is a browser bug, possibly relating to fact that the colors white, black, transparent, and any true neutral gray can be represented multiple ways all the polar color spaces. oklch(100% 0 0deg) is white, but so is oklch(100% 0 240deg). The first is a “red” white with no chroma, and the second is a “blue” white with no chroma.

For some reason, the color white is interpreted as having just the tiniest bit of chroma, but in a yellow-green hue. Below is an incorrectly interpreted blue to white gradient, followed by a correctly interpreted gradient to oklch(100% 0.001 90deg). The two appear exactly the same, and I think for some reason this shows how the browsers are converting white to OKLCH. The result occurs if you specify white in a non-oklch color syntax, including hex, RGB, and HSL.

oklch — blue to white
oklch — blue to oklch(100% 0.001 90deg)

Thankfully, there’s a workaround: specify the neutral color in the same color space as the gradient, and match the hue: oklch(100% 0 264deg). This seems to fix the issues in Safari and about half of the cases in Chrome. To fix those last remaining bugs in Chrome, add back just a smidge of chroma: oklch(100% 0.001 264deg). This trick works for black, gray, and transparent. Here are all the same gradients as above, but corrected:

oklch — blue to oklch(100% 0.001 264deg)
oklch — blue to oklch(50% 0.001 264deg)
oklch — blue to oklch(0% 0.001 264deg)
oklch — blue to oklch(0 0.001 264deg / 0)

And corrected red gradients:

oklch — red to oklch(100% 0.001 29deg)
oklch — red to oklch(50% 0.001 29deg)
oklch — red to oklch(0% 0.001 29deg)
oklch — red to oklch(0 0.001 29deg / 0)

Workarounds in HSL

Some HSL gradients suffer from the same issues. Red gradients seem to behave as expected, but these blue gradients all have some vibrant purple in the middle:

hsl — blue to white
hsl — blue to gray
hsl — blue to black
hsl — blue to transparent

A similar approach can fix this, but it’s a little more finnicky. For white, black, and transparent, you need to set the saturation to 100%. Saturation as low as 1% can work, but I've found that in Safari, it’s very particular based on the precise luminescent value. Using 100% seems to be the most reliable fix, and this is fine because at these lightness levels, saturation is irrelevant anyway.

Dark grays can be problematic. Chrome seems to handle this fine, but in Safari, variations of a tenth of a percent can have wild effects on the result. In the following gradients, lightness of 10% and 9.7% work fine, but values between that inexplicably result in a purple gradient:

hsl — blue to hsl(240deg 1% 10%)
hsl — blue to hsl(240deg 1% 9.9%)
hsl — blue to hsl(240deg 1% 9.8%)
hsl — blue to hsl(240deg 1% 9.7%)

In these cases, a slighly higher saturation (at least 2%) seems to fix the issues. Here are fixed gradients in HSL:

hsl — blue to hsl(240deg 100% 99.9%)
hsl — blue to hsl(240deg 100% 0.1%)
hsl — blue to hsl(240deg 2% 50%)
hsl — blue to hsl(240deg 100% 1% / 0)

Issues in LAB and LCH color

As a final note, the LAB and LCH color spaces also display unexpected hues in some gradients, most notably a purple showing up when the gradient should be blue. The bug in these cases lies in the definitions of the color spaces themselves, and not in the browser’s interpretation.

You should avoid these color spaces and use the superceding ones instead: OKLAB and OKLCH.

Loading interactions…

Recent Posts

See all posts