CSS Gradient Generator_

Build a linear, radial or conic gradient from a list of colour stops, drag the angle, and copy CSS that a browser will actually accept. Stops take hex, rgb() or hsl(), with an optional percentage to pin them.

The control that matters is the one most generators do not have. A gradient from #0000ff to #ffff00 interpolated in sRGB passes through the average of the two, which is #808080 — neutral grey, right across the middle. Naming a better interpolation space fixes it, and this page measures how much each one recovers instead of asserting that it helps.

toolkit.codes/css-gradient-generator
Preview
Add at least two colour stops
UTF-8
Ready
100% LOCAL
Input
Colour stops, one per line, in hex, rgb() or hsl(), each with an optional percentage position.
Output
A CSS gradient value with the sRGB fallback declaration above the wide-gamut one, plus the colour loss measured in all four spaces.
Processing
Stops are resolved by the CSS position rules, interpolated with premultiplied alpha, and sampled 51 times per space to measure the dip.
Limits
Interpolating in a wider space introduces hues you did not name — blue to yellow through oklch passes through green. That is a design decision, not a default.
The measurement
Blue to yellow loses 100% of its chroma in sRGB, 73% in oklab and 47% in oklch. OKLab helps a great deal and does not cure it, because a straight line between opposite hues still passes near neutral.

The grey stripe down the middle

One line of arithmetic explains it

A CSS gradient with no interpolation keyword blends in sRGB, and blending in sRGB means averaging three channel intensities. Halfway between #0000ff and #ffff00 every channel is (0 + 255) / 2, so the midpoint is #808080. Not a muddy blue-green, not a dull olive — neutral grey with a measured chroma of zero, sitting between two of the most saturated colours the format has. The same thing happens to red and cyan, and to green and magenta, for the same reason: opposite hues cancel when you average their channels.

Why the usual workaround is a midpoint stop

The traditional fix is to add a third stop by hand in the middle — something colourful to steer the ramp around the dead zone. It works, and it means every gradient in the design system carries a hand-tuned stop that exists only to route around a defect in the maths. Change either end and the midpoint has to be retuned. Naming an interpolation space removes the need for it entirely, and the stop list goes back to describing what you actually wanted.

oklab helps a great deal and is not a cure

This is the part usually left out. Switching to in oklab takes the blue-to-yellow chroma loss from 100% down to 73% — a huge visible improvement, and still a noticeable flattening. OKLab is perceptual but it is still rectangular: interpolating it draws a straight line between two points, and the straight line between two opposite hues passes close to the neutral axis in the middle. Less grey than sRGB, not ungrey.

oklch goes around instead of through

in oklch interpolates the hue as an angle, so the ramp travels around the neutral axis rather than across it, and the chroma survives: the same gradient loses 47%. The catch is that going around the circle passes through hues you never named — blue to yellow in oklch runs through green. Whether that is the right answer depends on whether you wanted a blend of two colours or a sweep between them, which is why this page shows all four spaces at once and lets you decide rather than picking for you.

Stops, space, angle, copy

  1. 01Type your colour stops one per line. Hex, rgb() and hsl() all work, and a trailing percentage pins a stop where you want it.
  2. 02Look at the four-space comparison. Each bar is the same gradient with a different interpolation space, and the figure beside it is how much colour the ramp loses across the middle.
  3. 03Pick the space that suits the design, then drag the angle — or switch to radial or conic, where the same interpolation choice applies.
  4. 04Copy the CSS. It comes as two declarations: the plain sRGB gradient first, the wide one second, so a browser that does not understand the keyword still gets a gradient rather than none.

Four gradients and the space each one wants

A hero banner between brand colours

Two saturated, near-opposite hues that go dull in the middle.

sRGB
loses 100% of its chroma
in oklab
loses 73% — no hand-placed midpoint stop

A rainbow sweep for a chart legend

You want the hues in between, so going round the circle is the point.

CSS
conic-gradient(from 0deg in oklch, ...)
Result
even hue steps, chroma held

A fade to transparent over a photo

Alpha is premultiplied, so the fade keeps its colour on the way out.

Stops
rgba(255,87,51,1) → rgba(255,87,51,0)
Midpoint
still rgb(255, 87, 51) at 50% alpha

A two-tone diagonal split

Two stops sharing a position render as an edge, not a blend.

Stops
#1a1a1a 50% then #3c83f6 50%
Result
a hard 45° line, no interpolation at all

What each interpolation space does to a blue-to-yellow ramp

SpaceColour lost across the middleMidpointWhen to reach for it
srgb (the default)100%#808080Only when you specifically want the legacy look, or every stop is already close in hue
oklab73%#6cabc7The safe general upgrade — a straight perceptual blend between two colours
oklch47%#00bdb0When the ramp should stay colourful and passing through other hues is acceptable
hsl31%#00ff80Rarely — it holds chroma but its lightness is not perceptual, so the ramp bunches

Measured on this page's own engine by sampling the ramp 51 times and comparing the least colourful point with the stops themselves. Different stops give different numbers; the tool recomputes them for whatever you type.

The four hue interpolation methods

KeywordRouteRed to green passes through
shorter hue (default)The smaller of the two arcsOrange and yellow
longer hueThe larger arc, the other way roundMagenta, blue and cyan
increasing hueAlways upward in degreesOrange and yellow, here the same as shorter
decreasing hueAlways downward in degreesMagenta, blue and cyan, here the same as longer

Only meaningful in a polar space, so the control appears when oklch or hsl is selected. Whether increasing matches shorter depends on the stops — for red to green they coincide, for other pairs they do not.

Habits that make gradients behave

  • Reach for in oklab as the default upgrade. It is the smallest change that removes most of the problem and it never introduces a hue you did not ask for.
  • Delete the hand-placed midpoint stops when you switch space — they were compensating for the old maths and will now push the ramp somewhere odd.
  • Ship both declarations. A browser that does not parse the keyword discards the whole line, so without the fallback the element gets no gradient at all rather than a worse one.
  • For a fade to transparent, use the same colour at both ends with different alpha. Fading to a bare transparent keyword is fading to transparent black, which greys the tail in sRGB.
  • Two stops at the same percentage make a hard edge. That is the cheapest way to draw a stripe or a two-tone split without an extra element.
  • Check a gradient at the size it ships. Banding is far more visible across a wide hero than across a small button, and a shallow angle stretches the ramp further than a steep one.

Where a gradient goes wrong

The interpolation keyword is not a separate argument

It belongs inside the first component, beside the angle: linear-gradient(90deg in oklab, ...). Writing linear-gradient(in oklab, 90deg, ...) puts a comma between them, so the browser reads 90deg as a colour stop and throws the entire declaration away. The element then has no gradient at all. This page emits the correct form, verified against a real browser.

A wider space can produce colours the screen cannot show

Interpolating in oklab or oklch can land on values outside sRGB partway along the ramp. Those get brought back by reducing chroma, not by clamping channels — clamping would rotate the hue exactly where the gradient is most colourful, which is the failure the OKLCH to HEX converter demonstrates in detail. Browsers do their own mapping, so a preview elsewhere may differ slightly.

Fading to transparent is not the same as fading out

The transparent keyword means transparent black. In sRGB the ramp travels toward black as it fades, which reads as a dirty grey edge. Use the same colour with zero alpha at the far end instead — alpha is interpolated premultiplied, so the colour holds all the way.

Banding is a display problem, not a maths problem

Eight bits per channel is not many steps across a large area, and a subtle gradient will show bands whatever space it was built in. Adding a faint noise overlay hides it far better than adding stops, which only moves the bands around.

A position that goes backwards is silently clamped

CSS requires stop positions to be non-decreasing, so a stop at 20% written after one at 80% is raised to 80% rather than rejected. That produces a hard edge where a blend was intended. This page resolves positions the same way a browser does, so what you see here is what will ship.

Interpolation, positions and output

Accepted stops
One colour per line in hex, rgb()/rgba() or hsl()/hsla(), each with an optional trailing percentage. Blank lines are ignored and a bad line is reported by number
Position resolution
The CSS rules: a missing first position is 0 and a missing last is 1, runs of unpositioned stops spread evenly between their anchors, and any position below the previous one is raised to match
Interpolation spaces
srgb (gamma-encoded, the CSS default), oklab, oklch and hsl — all four are real CSS Colour Level 4 keywords supported by current browsers
Hue methods
shorter, longer, increasing and decreasing, implemented from CSS Colour Level 4 §12.4 and offered only for the polar spaces where they mean anything
Alpha
Premultiplied before interpolation and undone after, as CSS specifies, so a fade to zero alpha keeps its colour instead of drifting. Hue angles are never scaled by alpha
Gamut
A colour landing outside sRGB partway along the ramp is brought back with the CSS Colour 4 §13.2 mapping shared with the OKLCH to HEX converter, not by clipping channels
Measurement
The ramp is sampled 51 times per space and each sample’s chroma read in OKLCH; the figure shown is how far the least colourful point falls below the least colourful stop
Output
Two declarations — the sRGB gradient, then the chosen one — because a browser that cannot parse the keyword drops the whole line
Network
None from tool code. A test sweep calls every function this page uses with fetch and XMLHttpRequest replaced by stubs that throw, so a stray request fails the build instead of shipping. Disconnect from the network and the page still works.

Questions about CSS gradients

Why does my CSS gradient look grey in the middle?

Because it is interpolating in sRGB, which averages three channel intensities. Between two opposite hues those intensities cancel: halfway from #0000ff to #ffff00 every channel is (0 + 255) / 2, giving exactly #808080. Add in oklab after the angle and the grey band largely disappears.

What does "in oklab" do in a gradient?

It tells the browser which colour space to blend in. OKLab is perceptual, so the intermediate colours are the ones the eye expects rather than the ones channel arithmetic produces. On a blue-to-yellow ramp it takes the colour loss from 100% down to 73% — a big improvement, though not a complete fix, since a straight line between opposite hues still passes near neutral.

Should I use oklab or oklch for gradients?

oklab for a straight blend between two colours, which is what most gradients want. oklch when the ramp must stay vivid and you are happy for it to travel through intermediate hues — it interpolates the hue angle, so blue to yellow goes via green. oklch also gives you the shorter and longer hue keywords, which decide which way round the circle the ramp travels.

How do I make a gradient fade out cleanly?

Use the same colour at both ends with different alpha rather than fading to the transparent keyword, which means transparent black and drags the tail toward grey. Alpha is interpolated premultiplied, so a fade from rgba(255,87,51,1) to rgba(255,87,51,0) keeps that orange the whole way.

Do I need a fallback for the interpolation keyword?

Yes. A browser that cannot parse it discards the entire declaration, so the element ends up with no gradient rather than a plainer one. Write the sRGB version first and the wide one immediately after — the browser takes the last declaration it understands, and no feature query is needed. This page emits both.

Why did my gradient disappear when I added the space?

Almost certainly a stray comma. The interpolation method sits beside the angle inside the same component: linear-gradient(90deg in oklab, red, blue). Writing linear-gradient(in oklab, 90deg, red, blue) makes 90deg look like a colour stop, the value fails to parse, and the declaration is dropped.

Do the gradients I build here get uploaded?

The work is JavaScript running in this tab. Every function it calls is covered by a test that stubs fetch and XMLHttpRequest to throw, so a request that slipped in would break the build rather than reach a server — and you can confirm it for yourself by disconnecting and carrying on.