Gradients

CSS · Lesson 8 of 55 9 min read Updated

A gradient is a smooth change from one colour to another. Buttons, hero sections, progress bars, pie charts and even ruled notebook paper can all be drawn with gradients, with no image file to download and no blurry edges when the screen is zoomed.

In this lesson you will learn the three kinds of gradient, how colour stops and hints control the blend, how to repeat a gradient to make patterns, how to get brighter blends with oklch, and two popular tricks: gradient text and gradient borders. How to stack gradients with photos is covered later in Backgrounds & Gradients.

A gradient is an image

This is the first thing to understand: in CSS a gradient is an image, not a colour. The browser draws it at whatever size the element has. So it goes where an image goes, usually background-image or the background shorthand.

CSS
.banner {
  background-image: linear-gradient(#ffedd5, #fed7aa);
}

/* Wrong: background-color only accepts a single colour, so this line is ignored */
.wrong {
  background-color: linear-gradient(#ffedd5, #fed7aa);
}

Because it is an image, a gradient can also be used in border-image, mask-image and list-style-image. You will meet masks in Clip-path, Shapes & Masks.

Linear gradients

linear-gradient() blends colours along a straight line. With no direction it runs from top to bottom. You can name a direction with to and a side or corner, or give an angle:

  • to right, to top, to bottom left: the side or corner the gradient travels towards.
  • 0deg points up, 90deg points right, 180deg points down (the default).
HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Linear gradients</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 1rem; display: flex; flex-wrap: wrap; gap: 1rem; }
    .box { width: 10rem; height: 7rem; border-radius: 10px; display: grid; place-content: center; font-weight: 600; }
    .a { background-image: linear-gradient(#bae6fd, #0369a1); }
    .b { background-image: linear-gradient(to right, #ff9933, white, #138808); }
    .c { background-image: linear-gradient(135deg, #fde68a, #f97316, #be123c); }
    .d { background-image: linear-gradient(to top right, #1e3a8a, #93c5fd); color: white; }
  </style>
</head>
<body>
  <div class="box a">Default</div>
  <div class="box b">to right</div>
  <div class="box c">135deg</div>
  <div class="box d">to top right</div>
</body>
</html>

Change 135deg to 45deg and run it again to see the line turn. Add more colours and the browser spreads them out evenly.

Colour stops and hints

Each colour in the list is a colour stop. You can say exactly where a stop sits with a length or percentage after the colour. The browser blends smoothly between neighbouring stops.

  • #f97316 30% puts that colour at 30% of the line.
  • #f97316 30% 60% (two positions) holds the colour solid from 30% to 60%.
  • Two stops at the same position make a hard edge with no blend at all.

A colour hint is a bare position between two stops, with no colour. It moves the midpoint of the blend. Normally the half-way mix sits exactly between the stops; a hint of 20% pulls it to 20%, so the second colour takes over sooner.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Stops and hints</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 1rem; }
    .bar { height: 3rem; margin-block: 0.4rem 1.2rem; border-radius: 6px; }
    .even  { background: linear-gradient(to right, #fde047, #7c3aed); }
    .hint  { background: linear-gradient(to right, #fde047, 20%, #7c3aed); }
    .solid { background: linear-gradient(to right, #fde047 0 30%, #7c3aed 70% 100%); }
    .hard  { background: linear-gradient(to right, #fde047 50%, #7c3aed 50%); }
  </style>
</head>
<body>
  <p>Two stops, blended evenly</p>   <div class="bar even"></div>
  <p>A hint at 20% moves the midpoint</p> <div class="bar hint"></div>
  <p>Solid from 0 to 30% and 70% to 100%</p> <div class="bar solid"></div>
  <p>Same position: a hard edge</p>  <div class="bar hard"></div>
</body>
</html>

Compare the second bar with the first: the purple arrives much earlier, but the start and end colours are the same. The third bar blends only in the middle 40%. The last bar is a sharp split, which is the basis of stripes and flags.

Radial gradients

radial-gradient() spreads colours outwards from a centre. The default shape is an ellipse that fits the box, centred, and reaching the farthest corner. You can change all of that:

CSS
.spotlight {
  /* shape, size and position come first, then the stops */
  background: radial-gradient(circle at 30% 25%, #fef9c3, #f59e0b 40%, #78350f);
}
.glow {
  /* closest-side: the circle stops at the nearest edge of the box */
  background: radial-gradient(circle closest-side, #a5f3fc, #0e7490);
}

The size keywords are closest-side, closest-corner, farthest-side and farthest-corner (the default). You can also give an exact size, such as circle 80px.

Conic gradients

conic-gradient() sweeps colours around a centre point, like the hand of a clock. It starts at the top and turns clockwise. Use from to rotate the start and at to move the centre.

CSS
.pie {
  border-radius: 50%;
  /* Rohan's monthly budget: rent 45%, food 30%, travel 15%, savings 10% */
  background: conic-gradient(#2563eb 0 45%, #16a34a 0 75%, #f59e0b 0 90%, #db2777 0);
}
.wheel {
  border-radius: 50%;
  background: conic-gradient(from 90deg, red, yellow, lime, aqua, blue, magenta, red);
}

A stop position of 0 means "start where the previous colour ended". That gives hard edges between slices without repeating every number.

Repeating gradients

repeating-linear-gradient(), repeating-radial-gradient() and repeating-conic-gradient() take the stops you give and repeat them until the box is full. The last stop position sets the length of one repeat. This is how you draw stripes, rings and checkerboards.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Repeating gradients</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 1rem; display: flex; flex-wrap: wrap; gap: 1rem; }
    .notebook {
      width: 18rem;
      padding: 0 1rem 0 3.5rem;
      line-height: 1.75rem;
      background:
        linear-gradient(to right, transparent 2.8rem, #ef4444 2.8rem 2.9rem, transparent 2.9rem),
        repeating-linear-gradient(white 0 calc(1.75rem - 1px), #93c5fd calc(1.75rem - 1px) 1.75rem);
    }
    .stripes {
      width: 10rem; height: 1.5rem; border-radius: 999px;
      background: repeating-linear-gradient(-45deg, #22c55e 0 10px, #16a34a 10px 20px);
    }
    .checker {
      width: 8rem; height: 8rem;
      background: repeating-conic-gradient(#e2e8f0 0 25%, white 0 50%) 0 0 / 2rem 2rem;
    }
  </style>
</head>
<body>
  <p class="notebook">Class 7 homework, Meera: write ten lines about your favourite festival and why your family enjoys it so much every year.</p>
  <div>
    <div class="stripes"></div>
    <p>Upload progress</p>
    <div class="checker"></div>
  </div>
</body>
</html>

The notebook uses two layers: a thin red margin line on top and repeating blue rules below. Each rule repeats every 1.75rem, the same as the line-height, so the writing sits on the lines. The checkerboard repeats a four-slice conic gradient inside each 2rem tile.

Brighter blends with oklch

Blend two strong colours, such as blue and orange, and the middle often turns a muddy grey. That happens because gradients between ordinary hex or rgb() colours blend in the sRGB colour space by default. You can choose a different interpolation colour space right after the direction:

CSS
.button {
  background: linear-gradient(to right, #2563eb, #f97316);           /* fallback */
  background: linear-gradient(to right in oklch, #2563eb, #f97316);  /* livelier middle */
}
.rainbow {
  /* same start and end colour, but go the long way round the colour wheel */
  background: linear-gradient(to right in oklch longer hue, oklch(70% 0.18 30), oklch(70% 0.18 30));
}

in oklch and in oklab blend the way our eyes see colour (see CSS Colours). With oklch, you can also choose how the hue travels: shorter hue (the default) or longer hue, which passes through every colour of the rainbow. This syntax works in current versions of all major browsers. Older browsers drop the whole declaration, so keep the plain gradient on the line before as a fallback.

Gradient text

To fill letters with a gradient, paint the gradient as a background, then clip the background to the shape of the text and make the text colour transparent:

CSS
.headline {
  color: #7c3aed;          /* solid fallback */
  width: fit-content;      /* the gradient spans the text, not the whole row */
}
@supports (background-clip: text) or (-webkit-background-clip: text) {
  .headline {
    background: linear-gradient(90deg in oklch, #f97316, #db2777, #7c3aed);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

The unprefixed background-clip: text works in current browsers, but older Chrome and Safari versions needed the -webkit- form, so many sites write both. The @supports check, explained in @supports Feature Queries, makes sure the text never becomes invisible in a browser that cannot clip. Check that every part of the gradient has enough contrast with the page background.

Gradient borders

border-color only takes a single colour. To get a gradient border that also follows rounded corners, give the element a transparent border and two background layers: a plain one clipped to the padding box on top, and the gradient on the full border box underneath.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Gradient border and text</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 1.5rem; background: #f8fafc; }
    .plan {
      max-width: 18rem;
      padding: 1.25rem;
      border: 3px solid transparent;
      border-radius: 16px;
      background:
        linear-gradient(white, white) padding-box,
        linear-gradient(135deg in oklch, #f97316, #db2777, #7c3aed) border-box;
    }
    .plan h2 { margin: 0 0 0.5rem; width: fit-content; color: #7c3aed; }
    @supports (background-clip: text) or (-webkit-background-clip: text) {
      .plan h2 {
        background: linear-gradient(90deg in oklch, #f97316, #7c3aed);
        -webkit-background-clip: text;
        background-clip: text;
        color: transparent;
      }
    }
  </style>
</head>
<body>
  <article class="plan">
    <h2>Pro plan</h2>
    <p>Unlimited mock tests for Class 12 board exams, ₹1,299 per year.</p>
  </article>
</body>
</html>

The white layer hides the gradient everywhere except the 3px border ring, and both layers follow border-radius.

Animating a gradient

A transition from one gradient to another does not animate: the browser simply swaps the images. To animate a gradient, register a custom property with @property (see Custom Properties) and animate that value instead:

CSS
@property --angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}
.live-badge {
  background: conic-gradient(from var(--angle), #f97316, #7c3aed, #f97316);
  animation: turn 3s linear infinite;
}
@keyframes turn {
  to { --angle: 360deg; }
}
@media (prefers-reduced-motion: reduce) {
  .live-badge { animation: none; }
}

Because the browser knows --angle is an angle, it can work out every value in between, and the colours rotate smoothly.

Common mistakes

  • Putting a gradient in background-color. It is an image; use background-image or background.
  • Writing background after background-clip. The shorthand resets the clip to its default. Put the shorthand first.
  • Expecting 90deg to point down. Angles start at the top and turn clockwise, so 90deg points right.
  • Invisible gradient text. Without a fallback, color: transparent hides the text where clipping is not supported.
  • Low contrast on part of the gradient. Text that is readable on the dark end may vanish on the light end.

Key points

  • Gradients are images drawn by the browser; use them in background-image or background.
  • linear-gradient() follows a line, radial-gradient() spreads from a centre, conic-gradient() sweeps around a point.
  • Stop positions control where colours sit; equal positions make hard edges, and a colour hint moves the midpoint.
  • repeating-* gradients repeat a pattern for stripes, rules, rings and checkerboards.
  • Add in oklch for livelier blends, with a plain gradient as a fallback line.
  • Gradient text uses background-clip: text; gradient borders use padding-box and border-box background layers.

Exercise

Make a "Diwali Sale" banner for a Pune electronics shop:

  • a background that blends from deep purple to orange using in oklch, with a plain fallback line;
  • a heading with gradient text and a solid fallback colour;
  • a "Shop now" link inside a box with a 2px gradient border and rounded corners.
Show solution
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Diwali Sale</title>
  <style>
    body { font-family: system-ui, sans-serif; margin: 0; }
    .banner {
      padding: 2.5rem 1.5rem;
      background: linear-gradient(120deg, #4c1d95, #f97316);
      background: linear-gradient(120deg in oklch, #4c1d95, #f97316);
    }
    h1 { margin: 0 0 1rem; font-size: 2.5rem; width: fit-content; color: #fef3c7; }
    @supports (background-clip: text) or (-webkit-background-clip: text) {
      h1 {
        background: linear-gradient(90deg, #fef3c7, #fde047);
        -webkit-background-clip: text;
        background-clip: text;
        color: transparent;
      }
    }
    .cta {
      display: inline-block;
      padding: 0.6rem 1.2rem;
      border: 2px solid transparent;
      border-radius: 999px;
      color: #4c1d95;
      font-weight: 700;
      text-decoration: none;
      background:
        linear-gradient(white, white) padding-box,
        linear-gradient(90deg, #fde047, #db2777) border-box;
    }
  </style>
</head>
<body>
  <section class="banner">
    <h1>Diwali Sale</h1>
    <a class="cta" href="#offers">Shop now</a>
  </section>
</body>
</html>

Each modern feature has a fallback: the plain gradient line before the in oklch line, and the solid heading colour outside @supports. The link's white layer covers the padding box, so the gradient shows only in the transparent border.

Quick Quiz

1. Why is background-color: linear-gradient(red, blue); ignored?

2. What does linear-gradient(to right, gold 50%, navy 50%) draw?

3. In linear-gradient(red, 20%, blue), what is the 20%?

4. Which technique gives a gradient border that follows border-radius?

Learn CSS with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call