A little help goes a long way. Find a tool/

Design FIELD NOTES · 4 MIN READ

How CSS Gradients Work

A plain-English tour of CSS gradients: linear, radial, and conic functions, angle conventions, color stops and hard edges, alpha stops, and contrast over fades.

A CSS gradient looks like an effect, but it is really a kind of image: the browser computes a picture from a formula — colors along a line, out from a point, or sweeping around one — and paints it as the background. Three functions generate every gradient on the web, and their angles, stops, and color formats follow a few rules worth knowing once.

A gradient is an image, not a color

linear-gradient() and its siblings are functions that produce images, so they belong in background-image (or the background shorthand), not background-color — a common slip that makes the declaration appear to "do nothing". The generated image scales to fill the element's box by default, and like other background layers it stacks: several gradients can be listed comma-separated with the first one painted on top, which is how layered overlays are built.

linear-gradient: colors along a line

A linear gradient travels in one direction across the box. The angle is measured so that 0deg points up, 90deg points right, and larger angles rotate clockwise:

background-image: linear-gradient(90deg, #4f8cff, #22c55e);
/* left edge #4f8cff, sliding to #22c55e at the right edge */

Direction keywords work too — to top right, to bottom — and translate to the nearest equivalent angle. The mental model that survives contact with reality: the angle describes where the last color arrives, and the color bands run perpendicular to that direction. Rotate the angle by 90° and the stripes turn with it.

radial and conic: out from a point, around a point

radial-gradient() spreads outward from a center point, so the color bands are rings — the default shape is an ellipse sized to the box, with circle forcing round rings:

background-image: radial-gradient(circle, #4f8cff, #22c55e);

conic-gradient() sweeps around a point like the hand of a clock, starting at 12 o'clock by default and rotating clockwise — the function behind pie charts, color wheels, and loading rings:

background-image: conic-gradient(from 90deg, #4f8cff, #22c55e);

The two are easy to confuse because both are "centered". If the bands are rings, it is radial; if they are wedges, it is conic.

Stops, positions, and hard edges

Each color in the list is a color stop. By default the browser spreads them evenly from 0% to 100% along the gradient line, interpolating smoothly between neighbors. Explicit positions reshape that curve — and a "double stop" (the same color at two positions) creates an instant transition, the trick behind stripes and segmented rings:

background-image: linear-gradient(
  90deg,
  #4f8cff 0%,
  #4f8cff 50%,
  #22c55e 50%,
  #22c55e 100%
);
/* left half solid #4f8cff, right half solid #22c55e */

Modern CSS shortens each pair with double positions: #4f8cff 0 50% means "this color holds from 0% through 50%". Anything left of the first stop stays that first color, and anything past the last stop stays the last — gradients do not fade to nothing at the edges unless you say so.

When hex is not enough: rgb() and alpha

Six-digit hex colors cannot express transparency, and gradients constantly need it — overlays that fade to nothing, glassy panels, image scrims. That is where rgb() earns its keep: #4f8cff is rgb(79, 140, 255), and appending an alpha gives rgba(79, 140, 255, 0), a fully transparent version of the exact same color. That precision matters because a fade "to transparent" should fade to transparent blue, not to opaque gray: rgba(79, 140, 255, 0) interpolates cleanly, while a stop of transparent blends through black in most browsers. This is also why 8-digit hex exists — #4f8cff00 — though rgb()/rgba() remains the more readable form when you are doing arithmetic on the channels.

Text on a gradient: check the worst stop

A gradient background is a contrast problem with a moving target. WCAG requires a 4.5:1 ratio for normal text (3:1 for large text) against the background behind each glyph, so a white heading that passes on a dark navy stop can fail where the gradient lightens. The practical rule: check the text color against the lightest stop and the darkest stop it touches — if the text sits in the middle of the fade, both ends must pass. If they cannot, shift the stop positions so the text region of the gradient stays within a safe range, or add a scrim layer. The arithmetic behind those thresholds is the same relative-luminance formula described in how color contrast works.

Try it yourself

The CSS Gradient Generator composes linear, radial, and conic gradients from two colors plus an angle, with a live preview and a copyable background declaration. The Hex to RGB Converter does the channel arithmetic when a stop needs an alpha channel. And the Color Contrast Checker scores any foreground/background pair against the WCAG thresholds — run it on both ends of your gradient before shipping text over it.

Frequently asked questions

Why is my gradient not visible?
Gradients are images, so they belong in background-image or the background shorthand — assigning one to background-color does nothing. Also check that the element has a height: a zero-height box has nowhere to paint, and two identical stop colors produce a flat fill rather than a fade.
What does 90deg mean in linear-gradient?
0deg points up, 90deg points right, and angles increase clockwise — so linear-gradient(90deg, A, B) puts A at the left edge and B at the right. Keyword directions like "to top right" map to the nearest equivalent angle.
How do I make hard-edged stripes instead of a smooth fade?
Give the same color two positions: #4f8cff 0 50%, #22c55e 50% 100% produces two solid halves with an instant switch. The double-position shorthand (color start end) works in all modern browsers, and conic-gradient does the same for pie wedges.
Can a gradient stop be transparent?
Yes, but fade to a color-matched transparent rather than the keyword: convert #4f8cff to rgb(79, 140, 255) and use rgba(79, 140, 255, 0). A stop written as transparent blends through black in most browsers, which muddies the fade.
How do I keep text readable over a gradient?
Contrast is checked against the pixels actually behind the text, so test against both the lightest and darkest stops it overlaps — WCAG wants 4.5:1 for normal text, 3:1 for large. If the ends differ too much, flatten the gradient behind the text or add a scrim layer.