Design

Why CSS Gradients Go Grey in the Middle, and Two Ways to Fix It

Blue to yellow passes through dead grey because of arithmetic, not because your browser is broken. Here is the arithmetic, and the two fixes.

A CSS gradient goes grey in the middle because the browser blends your two colours one channel at a time, in sRGB, and the halfway point between two opposite hues is neutral. Take blue #0000FF to yellow #FFFF00: the red channel runs from 0 to 255, the green channel from 0 to 255, the blue channel from 255 to 0. Halfway along, all three sit at 127.5, which the browser paints as #808080. Changing the angle will not move it, because the angle is not what decides it.

That is the entire mechanism, and the fixes follow straight from it. Either give the blend a stop to travel through, or tell the browser to interpolate somewhere other than sRGB.

Which colour pairs go grey

The ones sitting roughly opposite each other on the colour wheel, because those are the pairs whose channels cancel. Hue is measured in degrees around the wheel, so the gap between your two ends predicts the damage before you render anything.

GradientHue gapsRGB midpointWhat you see
#0000FF#FFFF00180°#808080Dead mid grey
#00FFFF#FF0000180°#808080Dead mid grey
#FF00FF#00FF00180°#808080Dead mid grey
#FF0000#00FF00120°#808000Dark army olive
#6D5EFC#22D3EE58°#4899F5A clean blue

The last row is the one to learn from. Two hues a short walk apart never pass near the neutral axis, so nothing drains out of the middle. That is why so many gradients that look good on real sites turn out to be two colours that are nearly the same colour. Set both ends in the gradient generator and swap one end for a neighbouring hue: the middle refills as the gap closes, and you can watch the point where it stops being mud.

Grey is only the loudest failure, not the general one. The 120° pair does not go grey, it goes dark: red and green are both at 50% lightness, and olive #808000 is at 25%. Blue to yellow loses the opposite thing — it holds its 50% lightness all the way across and throws away every bit of saturation. What the arithmetic guarantees is that the midpoint is whatever averaging the channels produces, and the further apart the ends, the less that resembles either of them.

Fix 1: give the blend somewhere to go

Add a third stop in a hue you actually want, and the browser interpolates twice over a shorter distance each time instead of once through the middle of nowhere.

Blue sits at 240° on the wheel and yellow at 60°, which makes the two routes exactly the same length. Through green, the midpoint hue is 150° — #00FF80. Through magenta, it is 330° — #FF0080. Pick the one that belongs in your design:

background-image: linear-gradient(90deg, #0000FF, #00FF80, #FFFF00);

This works in anything that understands linear-gradient at all, which is every browser you are likely to meet. The catch is that you now have two ramps instead of one, and if the middle stop's lightness sits above or below both ends you get a visible bright or dark band exactly where you were trying to fix things. Choose the mid stop in HSL so you can hold saturation and lightness steady and move only the hue, then convert it back to hex for the stylesheet. That is the one job HSL is genuinely better at than hex, and the trade-offs between hex, RGB and HSL are worth knowing before you hand-tune a colour ramp.

One warning if you are adding the stop in the generator here rather than by hand: a new stop arrives coloured as the channel average of its two neighbours. For blue and yellow that average is #808080 — the tool hands you the exact grey you came to get rid of. Change its colour with the swatch. The position slider only moves where the stop sits, not what it is.

The percentage that does not help

A bare percentage between two colours is a colour interpolation hint. It moves the point where the two colours are mixed 50/50:

background-image: linear-gradient(90deg, #0000FF, 30%, #FFFF00);

People reach for this when the middle looks wrong, and it does not fix anything. The blend still travels the same path through colour space, so the grey is still there — it just arrives at 30% instead of 50%. A hint reshapes the timing of a blend. It cannot change where the blend goes.

Fix 2: interpolate in a different colour space

Modern CSS lets you name the space the browser blends in, right next to the angle:

background-image: linear-gradient(90deg in oklch, #0000FF, #FFFF00);

Two stops, no mud. OKLCH is a polar space: lightness, chroma, hue. Interpolating in it rotates the hue around the wheel while chroma moves in a straight line between the two ends, so the middle stays a saturated colour instead of collapsing towards neutral. It also spreads lightness evenly in a way that matches how eyes work rather than how the sRGB numbers happen to fall, which is why the result usually looks smoother even when grey was never the problem.

The honest caveat is gamut. Blue to yellow in OKLCH asks for a midpoint at roughly twice the chroma an sRGB screen can produce at that lightness and hue, so the browser maps it back inside the gamut. You get a saturated middle. You do not get the one the numbers describe.

By default the browser takes the shorter way round the wheel. Add longer hue to send it the other way and sweep through everything in between, which is how you get a rainbow out of two stops:

background-image: linear-gradient(90deg in oklch longer hue, #0000FF, #FFFF00);

OKLAB or OKLCH?

OKLAB is the same colour model in rectangular coordinates: lightness plus two axes. Blending in it fixes the lightness dip that makes sRGB gradients look dirty, and for most colour pairs that is enough. But a straight line drawn between two opposite hues passes close to the neutral axis in any rectangular space, so for genuinely complementary ends the OKLAB midpoint is still low in chroma — lighter and better behaved than #808080, but not vivid. If keeping the colour up through the middle is the whole point, use OKLCH and let it take the hue wheel instead.

The fallback you have to write yourself

An old browser does not ignore the in oklch part and carry on. It fails to parse the value, which makes the whole declaration invalid, and then you have no gradient at all. The fix is the standard CSS cascade trick — put the plain version first and the enhanced version second:

background-image: linear-gradient(90deg, #0000FF, #00FF80, #FFFF00);
background-image: linear-gradient(90deg in oklch, #0000FF, #FFFF00);

Anything that understands the second line uses it. Anything that does not keeps the first. Support for interpolation spaces is broad now, so this is insurance rather than a real constraint — but you cannot see what your visitors are running, and the failure mode is a blank background rather than a plain gradient. It costs one line.

Why fading to transparent can look grey too

This one has a different cause and catches people who are not blending two hues at all. In CSS, transparent is not "nothing" — it is defined as transparent black, rgba(0, 0, 0, 0). A naive blend from white to transparent therefore walks the colour towards black while the alpha walks towards zero, and the middle comes out as a grey haze over whatever is behind it.

The specification requires browsers to interpolate with premultiplied alpha, which avoids this, and current engines do. If you are still seeing the ghost — in an older engine, or in something that renders CSS but is not a browser — write the explicit zero-alpha version of your own colour instead of the keyword:

background-image: linear-gradient(180deg, #FFFFFF, #FFFFFF00);

It costs nothing, it is unambiguous, and it reads better than transparent anyway because the reader can see which colour is fading out.

Grey haze or grey stripes? Not the same bug

If the middle is not washed out but striped — visible bands stepping across a smooth blend — that is banding, and none of the fixes above touch it. Eight bits per channel gives 256 values. Stretch a subtle gradient across 1200 pixels and there are far more pixels than there are distinct colours to fill them, so the transition arrives in steps you can count. Increase the colour distance between the stops, shrink the area, or lay a faint noise texture over the top. Browsers dither a little and inconsistently, so the same gradient can band on one machine and look clean on the next.

The quick diagnostic: a muddy middle looks the same at every size and on every screen, because it is arithmetic. Banding changes when you resize the element, because it is quantisation.

What to do next time, before it goes grey

Decide the hue route before you pick the two ends. If the brief hands you two complementary colours, you are choosing between three outcomes: a third stop that commits to one side of the wheel, an OKLCH blend that commits for you, or a grey middle. There is no fourth option where sRGB quietly does the right thing. Gradients built from hues under about 60° apart never raise the question, which is a large part of why analogous colours keep turning up as the safe recommendation in guides to building a colour palette that holds together.

The fastest way to see any of this is to set both ends in the CSS gradient generator, add a third stop and pick its colour until the mud clears — the preview is your own browser rendering the same line you are about to paste, so nothing is being approximated. It writes plain sRGB in one layer, so if you want the OKLCH version you copy the CSS and add in oklch after the angle yourself.

Choosing that middle stop is a colour problem rather than a CSS one, and it goes much faster in HSL than in hex. Hex, RGB and HSL explained covers which format lets you hold a colour steady while you move one property of it, and where HSL's idea of lightness will mislead you.

Frequently asked questions

Why does my CSS gradient look grey in the middle?

Browsers blend gradient stops in sRGB, one channel at a time. When the two colours sit on opposite sides of the colour wheel, each channel averages out near the middle of its range, so the midpoint is close to neutral grey. Blue to yellow lands on #808080.

How do I stop a CSS gradient going grey?

Add a third stop in a hue you want the blend to pass through, or interpolate in a different colour space with linear-gradient(90deg in oklch, ...). The third stop works in every browser; the OKLCH version keeps two stops and needs a plain fallback declaration above it.

What does "in oklch" do in a CSS gradient?

It tells the browser to blend in OKLCH rather than sRGB. OKLCH is polar, so the hue rotates around the colour wheel instead of the blend cutting across the middle of it, which stops the midpoint collapsing towards grey. Adding "longer hue" sends the blend the other way round the wheel for a rainbow.

Is OKLAB or OKLCH better for gradients?

OKLAB fixes uneven lightness and is fine for most colour pairs. For two hues that are close to complementary, OKLAB still desaturates in the middle, because a straight line between opposite hues passes near the neutral axis in any rectangular colour space. OKLCH rotates the hue instead and keeps the colour up.

Why does a gradient fading to transparent look grey?

The CSS keyword transparent means transparent black, rgba(0, 0, 0, 0), so a naive blend darkens towards black as it fades out. Current browsers interpolate with premultiplied alpha and avoid it. If you still see a grey haze, write the zero-alpha version of your own colour, such as #FFFFFF00, instead of the keyword.

What is the difference between a muddy gradient and a banded one?

A muddy middle is a desaturated colour caused by the maths of sRGB interpolation, and it looks identical at every size. Banding is visible stripes caused by only having 256 values per channel across a large area, and it changes when you resize the element.

Last updated September 19, 2026