A linear gradient takes a direction followed by colour stops:
background: linear-gradient(135deg, #6366f1 0%, #ec4899 100%);
The angle convention is the usual first stumble. In CSS, 0deg points upward and angles increase clockwise:
| Angle | Direction | Keyword equivalent |
|---|---|---|
| 0deg | Upward | to top |
| 90deg | Rightward | to right |
| 180deg | Downward | to bottom |
| 135deg | Down and right | to bottom right, on a square |
Two traps live here. Mathematical convention puts 0 to the right and counts anticlockwise, so a gradient translated from a design tool is frequently rotated by 90 degrees. And the keyword to bottom right is not identical to 135deg — the keyword aims the gradient at the actual corner, so its angle changes with the box aspect ratio, while a degree value is fixed.
Useful when you want a stop to land on an exact pixel. For a box of width W and height H at angle A, the gradient line is
length = |W × sin(A)| + |H × cos(A)|
On a 400 by 200 box at 135deg, where sine and cosine are both 0.7071 in magnitude:
| Term | Value |
|---|---|
| 400 × 0.7071 | 282.84 |
| 200 × 0.7071 | 141.42 |
| Gradient line | 424.26 px |
So 50% is 212 pixels along that diagonal, not 200 across the width. Percentages are measured along the gradient line, which is why a stop that looks right on a wide element drifts when the element becomes tall.
Interpolate blue to yellow and the middle turns grey. This is not a browser bug — it is a consequence of averaging gamma-encoded sRGB channels. The numeric midpoint of #0000FF and #FFFF00 is #808080, a dead neutral, because the two colours sit on opposite sides of the colour cube and the straight line between them passes through its centre.
There are three ways out:
linear-gradient(in oklch, blue, yellow) travels around the hue circle instead of through the middle, and the result stays saturated across the whole run. This is the modern fix and it is a one-word change.A related trap: fading to transparent. That keyword expands to fully transparent black, so a white-to-transparent fade can pick up a grey cast in engines that do not premultiply. Writing the explicit rgba(255, 255, 255, 0) removes the ambiguity.
Subtle gradients show visible stripes because each channel has only 256 values. Take a background running from #1a1a2e to #16213e: the blue channel moves from 46 to 62, which is 17 distinct values for the whole width. Spread across 1,200 pixels that gives bands roughly 70 pixels wide — unmissable on a large screen.
| Channel travel | Bands available | Band width over 1,200 px |
|---|---|---|
| 16 values | 17 | 71 px |
| 60 values | 61 | 20 px |
| 200 values | 201 | 6 px |
The practical remedies are to increase the colour distance, to shorten the gradient, to overlay a faint noise texture that dithers the boundaries, or to add intermediate stops so the browser interpolates over shorter runs. Angled gradients band less visibly than perfectly horizontal or vertical ones, because the edges are not aligned with the pixel grid.
Placing two stops at the same position produces a hard edge instead of a blend — linear-gradient(90deg, #6366f1 50%, #ec4899 50%) is two solid halves, and repeating that pattern is how stripes, checkerboards and progress bars are drawn without images.
Radial gradients take a shape and a size, and the sizing keyword matters more than the colours: farthest-corner is the default, while closest-side produces a much tighter falloff. Conic gradients sweep around a centre point, which makes pie charts and colour wheels possible in pure CSS, and combined with repeating-conic-gradient they draw radial stripes.
Older tutorials, and many generators, still emit a stack of prefixed duplicates. That advice is not merely obsolete, it is actively harmful: -webkit-linear-gradient used the old angle convention, where 0deg pointed to the right. Pasting a prefixed line beneath an unprefixed one with the same angle value therefore describes a gradient rotated 90 degrees, and any browser that prefers the prefixed rule renders the wrong thing. Unprefixed gradients have been universally supported for over a decade. Emit one clean declaration.
Gradients are painted, not composited. A transform or an opacity change can be handled by the GPU without repainting; changing a gradient, or animating background-position across a large area, forces a repaint every frame and will drop frames on a big hero section or a low-powered phone.
Gradients are also not interpolatable by default, so a CSS transition between two background-image values snaps rather than blends. The workable patterns are to stack two gradient layers and animate the opacity of the top one, or to register a custom property with @property so its value becomes animatable, or to animate a transform on an oversized gradient element. And if a gradient animates continuously, honour prefers-reduced-motion — moving background colour is a common trigger for people with vestibular sensitivity.
Text over a gradient has varying contrast, and the standard tools check one pair of colours. Test the worst point along the run, not the average, or place a semi-transparent scrim behind the text so the ratio is constant.
Gradients are unreliable in email — desktop Outlook ignores background-image entirely, so a gradient there needs a fallback background colour that is legible on its own. Many browsers omit backgrounds when printing unless the user opts in. And a gradient can never be a substitute for meaning: if it distinguishes one state from another, that state needs a non-visual signal too.
Eight-bit banding. Widen the colour distance, add stops, or dither with a noise overlay — see the table above for how few steps a subtle gradient really has.
Different zero point. Design tools commonly measure from the right and anticlockwise; CSS measures from the top and clockwise.
Not directly. Cross-fade two stacked layers with opacity, which is also the cheaper option to render.
No. The preview and the generated CSS are produced in your browser, and nothing is transmitted or saved.
Every tool comes with a written guide, and every category is one click away.