A CSS gradient is one line of code, and most of the time it looks exactly as you expect. The exceptions are predictable: a diagonal that does not reach the corner, a muddy grey in the middle of two bright colours, stripes of flat colour on a large dark background. Each has a cause in how the browser computes the gradient, and each has a simple fix. This article goes through them with numbers computed by the geometry code of our CSS gradient generator.
Linear gradients: angle and the gradient line
background: linear-gradient(135deg, #ff512f 0%, #f09819 100%);135deg is measured clockwise from "up", so 0deg points to the top, 90deg to the right and 135deg towards the bottom right. The browser draws a gradient line through the centre of the box at that angle, and its length is chosen so that the 0% and 100% colours land exactly on the corners. For an angle a in a box w by h, the length is |w sin a| + |h cos a|.
In a 1200 × 600 banner, a 90deg gradient line is 1200 px long, the width of the box. At 135deg it is 848.5 + 424.3 = 1272.8 px. The stops you place in percent are percentages of that line, so the same 50% is 600 px from the start in one case and 636 px in the other.
135deg is not "to bottom right"
to bottom right is not a fixed angle. The spec defines it so that the middle colour runs exactly through the other two corners, which in a non-square box gives a different angle. In a square it is 135deg. In the 1200 × 600 banner it is 180 − atan(600 ÷ 1200) = 153.4deg. If a design uses to bottom right on a hero image and 135deg on a card, the two diagonals will not match. Use one form consistently: an angle when the slope matters, a keyword when the gradient must hit the corner whatever the shape.
Colour stops and hard edges
Each stop is a colour and a position. Between stops the colour is interpolated; before the first and after the last it is held. When two stops share a position, there is no space to blend, and you get a sharp edge:
linear-gradient(90deg, #ff0000 50%, #0000ff 50%)That is a box half red, half blue, with no image. Repeat the pattern and you have stripes:
repeating-linear-gradient(45deg, #222222 0%, #222222 5%, #ffcc00 5%, #ffcc00 10%)The pattern from 0% to 10% repeats along the line, giving the familiar yellow and black hazard stripes. Our generator draws repeating gradients the same way on the canvas for the PNG download, because canvas gradients do not repeat by themselves.
Radial and conic
A radial gradient spreads from a centre. Its default size is farthest-corner: the 100% stop lands on the corner furthest from the centre. With the centre at 600, 300 in an 800 × 400 box, that is a circle of radius √(600² + 300²) ≈ 670.8 px; as an ellipse centred in the same box the radii are 565.7 and 282.8 px. closest-side makes the gradient end at the nearest edge instead, which is what you want for a spotlight that should not spill:
radial-gradient(circle closest-side at 30% 40%, #ffffff 0%, #000000 100%)A conic gradient turns around the centre instead. Hard stops make it a pie chart without any library:
conic-gradient(#e63946 0%, #e63946 40%, #457b9d 40%, #457b9d 75%, #2a9d8f 75%, #2a9d8f 100%)That is slices of 40%, 35% and 25%, starting at the top and running clockwise. A pie built this way is an image to assistive technology, so put the numbers in text as well.
The grey zone in the middle
By default, CSS blends colours in sRGB, channel by channel. Blue #0000ff and yellow #ffff00 are opposites in every channel, so halfway the browser computes (127.5, 127.5, 127.5): a flat, neutral grey. Any two near-complementary colours do the same, to a lesser degree, which is why a quick purple-to-green gradient looks dirty in the middle.
Two fixes:
- Add a middle stop with a colour you choose, for example a light blue or a green between blue and yellow. This works in every browser.
- Interpolate in another colour space. CSS Color 4 lets you write
linear-gradient(in oklab, #0000ff, #ffff00), supported by current versions of the major browsers. In OKLab the same midpoint works out to about#6cabc7, a muted sky blue rather than grey. Our generator writes plain sRGB gradients, so add thein oklabpart by hand if you want it, and keep the version without it as a fallback for older browsers.
Fading to transparent
Fading a colour to transparent used to produce a dark smudge, because transparent is black with zero opacity and some older browsers blended the black in. Current CSS interpolates in premultiplied colour: the colour is weighted by its opacity before mixing. Halfway from opaque red to transparent, the result is red at 50% opacity, (255, 0, 0, 0.5), not dark red (127.5, 0, 0, 0.5). The generator's canvas output follows the same rule, so the PNG matches the CSS.
Banding
A screen pixel usually has 256 levels per channel. A gradient from #1e1e1e to #2a2a2a covers only 12 steps, so across a 1200 px wide hero there are just 13 distinct greys, each a band about 92 px wide. On a good display, those bands are visible as stripes. Subtle dark gradients are the worst case, and compressing a screenshot of one into JPEG makes it worse.
Fixes: widen the colour range, shorten the distance, add a faint noise texture on top, or accept a flat colour. Some browsers and GPUs dither gradients, but you cannot rely on it.
Common mistakes
- Mixing
to bottom rightand135degand expecting the same diagonal. - Blending complementary colours without a middle stop, then wondering where the grey came from.
- Putting body text over a gradient without checking contrast at both ends. Check the lightest and darkest points with the colour contrast checker; WCAG contrast ratio explained covers the thresholds.
- Long, low-contrast dark gradients on large screens, which band.
- Forgetting a fallback
background-colorfor the rare case where the gradient fails to parse, such as an unsupportedin oklab.
Build the gradient in the CSS gradient generator, copy the CSS, or download a PNG at the exact size you need. The live preview uses the same CSS you copy.