A CSS gradient is a generated image, which is why it can be used anywhere an image can. It paints a smooth ramp of colour across a shape, and the direction, the stops and the number of layers are what decide whether it looks like a background, a glow or a ring.
What does a gradient actually paint?
Every gradient is built from a shape — a line, a circle or a cone — and a list of colour stops placed along it. The browser fills the element with the colours, blending between neighbouring stops. What differs between the types is the shape of that geometry.
<section class="promo">
<p class="promo__kicker">New arrival</p>
<h2 class="promo__title">Field camera with a 35mm lens</h2>
<span class="chip">Save 20%</span>
</section>
<div class="ring" role="img" aria-label="68 percent of stock remaining"></div>
.promo {
background-image: linear-gradient(135deg, #7c3aed, #2563eb);
}
.chip {
background-image: radial-gradient(circle at 30% 20%, #ffd479, #f59e0b);
}
Because a gradient is an image rather than a paint, it interacts with the background properties you already know: background-size, background-position, background-repeat and background-clip all apply to it exactly as they apply to a photograph.
How is the angle of a linear gradient measured?
The gradient line has a direction, and the angle is measured clockwise from the vertical pointing up. So 0deg runs from bottom to top, 90deg runs from left to right, and 180deg runs from top to bottom. The default, to bottom, is the same as 180 degrees.
background-image: linear-gradient(to right, #0ea5e9, #6366f1);
background-image: linear-gradient(45deg, #f43f5e, #f59e0b);
background-image: linear-gradient(to bottom right, #0f172a, #334155);
Direction keywords are usually clearer than degrees. A corner keyword such as to bottom right does something more useful than a fixed 45 degrees: the browser works out the angle that points at that corner and shortens the gradient line so it ends at the corner rather than running past it. That means to bottom right looks the same on a wide banner and a tall card, while a hard 45 degrees does not.
How do colour stops work?
Stops are written as a colour followed by an optional position, and the position is a percentage of the gradient line. Two stops with no positions are placed at the two ends, three stops are spread evenly, and everything between them is blended.
/* even three-stop ramp */
background-image: linear-gradient(90deg, #22d3ee, #6366f1, #f472b6);
/* hard edge: two stops at the same position */
background-image: linear-gradient(90deg, #0f172a 50%, #e2e8f0 50%);
/* a long fade, then a flat run */
background-image: linear-gradient(180deg, rgba(15, 23, 42, 0) 0%, #0f172a 70%);
Two stops at the same position create a visible seam instead of a blend, which is a quick way to split a banner in two without a second element. If the first stop is given a position, everything before it is filled with that first colour, and if the last stop is given a position, everything after it is filled with the last colour. That is how you hold a flat run at one end of a ramp without repeating the colour.
Interpolation happens in premultiplied space, which is why a fade to transparent stays clean instead of drifting through a muddy grey. Where you want an even perceptual ramp, ask for a different space explicitly.
/* sRGB by default, oklab where browsers support it */
background-image: linear-gradient(in oklab, #ff0080, #0080ff);
How do you layer several gradients?
Because background-image takes a comma-separated list, several gradients can sit on one element. The first one listed is painted on top, exactly like the first background layer.
.cta {
border: 2px solid transparent;
background-image:
linear-gradient(#ffffff, #ffffff),
linear-gradient(120deg, #7c3aed, #2563eb);
background-origin: border-box;
background-clip: padding-box, border-box;
}
That block is the gradient border trick. The first layer is a solid white fill clipped to the padding box, and the second is the colourful gradient clipped to the border box, so the coloured layer shows only in the border area. It works with any border radius and needs no extra markup.
The same layering idea builds a soft glow: put a wide radial gradient with a transparent edge on top of a solid colour.
.glow {
background-image:
radial-gradient(60% 60% at 50% 0%, rgba(124, 58, 237, 0.35), transparent 70%),
#ffffff;
}
When do radial and conic gradients earn their place?
A radial gradient radiates from a centre. The shape is controlled by the size keywords and the position by the at syntax.
/* ellipse reaching the corners, centred at 30% 20% */
background-image: radial-gradient(ellipse at 30% 20%, #fff1b8, #f59e0b);
/* a circle of fixed size */
background-image: radial-gradient(circle 40px at center, #22d3ee, transparent);
A conic gradient sweeps colour around a centre point, like a slice of pie. Its main use is progress indicators, because a conic gradient can stop at an arbitrary angle and the rest of the ring can be a flat track.
.ring {
--p: 68; /* percent complete */
--brand: #2563eb;
--track: #e2e8f0;
width: 6rem;
aspect-ratio: 1;
border-radius: 50%;
background: conic-gradient(var(--brand) calc(var(--p) * 1%), var(--track) 0);
-webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - 0.75rem), #000 calc(100% - 0.75rem));
mask: radial-gradient(farthest-side, #0000 calc(100% - 0.75rem), #000 calc(100% - 0.75rem));
}
The mask punches a hole in the middle by staying transparent up to 0.75rem from the edge and opaque beyond it, and the conic gradient fills the arc up to the percentage you set. Changing one custom property redraws the ring, and because the mask is applied to the element rather than to a child, there is no extra markup to keep in sync.
What goes wrong with gradients in practice?
Three failures come up constantly. The first is text on a gradient with no scrim, which fails contrast on the light half of the ramp; put a dark overlay behind the text, or make the gradient dark enough everywhere. The second is banding, where a wide smooth ramp shows visible steps on an 8-bit display; interpolating in a wider space or overlapping two similar gradients hides it. The third is a gradient that hides the boundary of an interactive element, so the reader cannot tell where the button ends.
There is also a sizing detail worth remembering: a gradient covers the whole padding box of the element by default, and background-size changes that. A repeating pattern is made with repeating-linear-gradient() rather than by setting a small background-size, which is simpler and avoids seams.
Core ideas in one place
| Function | Geometry | Good for |
|---|---|---|
| linear-gradient | A straight line with an angle | Panel washes, overlays, hard splits |
| radial-gradient | An ellipse or circle from a centre | Glows, vignettes, spotlight effects |
| conic-gradient | A sweep around a centre | Progress rings, pie charts, colour wheels |
| repeating-linear-gradient | A straight line, repeated | Stripes and hatch patterns |
- Measure angles clockwise from the top, and prefer direction keywords over raw degrees.
- Stops are percentages along the gradient line; two stops at one position make a hard edge.
- List several gradients to layer them, and remember the first listed is on top.
- Clip a solid layer to the padding box to fake a gradient border without extra elements.
- Give text over a gradient a scrim, and keep gradients from hiding the edges of controls.
Try it yourself: Build a product page header for a small hardware shop. Give the banner a to bottom right linear gradient from #7c3aed to #2563eb, put white heading text on it with a 55 percent black scrim behind the text block, and add a 2px gradient border to the buy button using the two-layer background-clip trick. Then replace the button's flat fill with a conic gradient ring that shows 68 percent stock remaining, and confirm the heading still reads clearly against the lightest part of the gradient.