Type something to search...
Mastering CSS Gradients: Linear, Radial, and Conic

Mastering CSS Gradients: Linear, Radial, and Conic

Gradients used to mean exporting a PNG from Photoshop and hoping it didn't band on someone's monitor. Today, CSS gradients are resolution-independent images the browser draws on demand. They weigh nothing over the network, they scale to any size, and they can be animated, layered, and combined with custom properties.

There are three gradient families in CSS: linear, radial, and conic, each with a repeating variant. In this guide we'll go through all of them, look at how color stops and interpolation really work, and build practical patterns like striped progress bars, pie charts, and subtle background glows.

Gradients Are Images

The most important thing to understand is that a CSS gradient is an <image> value, not a color. That's why this doesn't work:

/* Invalid: background-color only accepts colors */
.hero {
  background-color: linear-gradient(#38bdf8, #818cf8);
}

And this does:

.hero {
  background-image: linear-gradient(#38bdf8, #818cf8);
}

Because gradients are images, they can go anywhere an image can: background-image, border-image, mask-image, list-style-image, and even content on pseudo-elements. They also obey all the background properties, like background-size, background-position, and background-repeat, which is the key to building patterns later on.


Linear Gradients

A linear gradient transitions colors along a straight line.

Direction

By default, the line runs from top to bottom. You can change it with a keyword or an angle:

.box-a {
  background-image: linear-gradient(to right, #38bdf8, #818cf8);
}

.box-b {
  background-image: linear-gradient(to bottom right, #f472b6, #fbbf24);
}

.box-c {
  background-image: linear-gradient(135deg, #34d399, #0ea5e9);
}

Angles start at the top (0deg) and go clockwise, so 90deg points right and 180deg points down. A small but useful detail: to bottom right isn't the same as 135deg on non-square elements. The keyword version angles the line so the corners get exactly the start and end colors, whatever the aspect ratio. The angle version keeps a fixed 135 degrees.

Color Stops

Each color can take a position, called a color stop:

.banner {
  background-image: linear-gradient(
    to right,
    #0f172a 0%,
    #1e3a8a 40%,
    #38bdf8 100%
  );
}

Anything before the first stop is filled with the first color, and anything after the last stop is filled with the last color. You can also give a single color two positions to create a solid band:

.band {
  background-image: linear-gradient(
    to right,
    #818cf8 0% 30%,
    #f472b6 30% 70%,
    #fbbf24 70% 100%
  );
}

When two neighboring stops share a position, the transition between them is instant. That's how you get hard stops, the basis for stripes, flags, and charts.

Color Hints

Between two stops, the transition is linear by default: the midpoint color appears halfway. A color hint moves that midpoint:

.fade {
  /* The midpoint shifts to 20%, so the fade happens early */
  background-image: linear-gradient(to right, #111827, 20%, #38bdf8);
}

Hints are handy for making fades feel more natural, such as a scrim over an image that should darken quickly near the bottom and stay clear above.

A Practical Pattern: Image Overlays

One of the most common uses for linear gradients is making text readable on top of photos:

<section class="hero">
  <img
    class="hero__img"
    src="/images/coast.jpg"
    alt="Rocky coastline at sunset"
  />
  <div class="hero__content">
    <h1>Explore the northern coast</h1>
  </div>
</section>
.hero {
  position: relative;
  display: grid;
  min-height: 70vh;
}

.hero__img,
.hero__content {
  grid-area: 1 / 1;
}

.hero__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hero__content {
  z-index: 1;
  display: flex;
  align-items: end;
  padding: 2rem;
  color: #fff;
  background-image: linear-gradient(
    to top,
    rgb(0 0 0 / 0.75),
    rgb(0 0 0 / 0) 60%
  );
}

The gradient runs from 75% black at the bottom to fully transparent at 60% of the height, so the photo stays vivid at the top while the headline stays legible.


Radial Gradients

A radial gradient radiates outward from a center point, as either an ellipse or a circle.

.spot {
  background-image: radial-gradient(#fbbf24, #f472b6);
}

With no arguments, the shape is an ellipse sized to reach the farthest corner of the box, centered in the element.

Shape, Size, and Position

The full syntax lets you control all three:

.glow {
  background-image: radial-gradient(
    circle at 20% 30%,
    #38bdf8,
    transparent 40%
  );
}
  • Shape : circle or ellipse.
  • Size : An explicit length such as 200px, or one of four keywords: closest-side, closest-corner, farthest-side, and farthest-corner (the default).
  • Position : After at, using the same syntax as background-position.

The size keywords matter more than they seem. If you want a circle that exactly touches the nearest edge, use closest-side:

.avatar-ring {
  width: 120px;
  aspect-ratio: 1;
  border-radius: 50%;
  background-image: radial-gradient(
    circle closest-side,
    #1f2937 85%,
    #818cf8 86% 100%
  );
}

Layered Glows

Radial gradients shine as soft background lighting. Because background-image accepts multiple layers, you can stack several:

.page {
  min-height: 100vh;
  background-color: #0f172a;
  background-image:
    radial-gradient(circle at 15% 20%, rgb(56 189 248 / 0.25), transparent 35%),
    radial-gradient(
      circle at 85% 10%,
      rgb(129 140 248 / 0.25),
      transparent 30%
    ),
    radial-gradient(circle at 50% 100%, rgb(244 114 182 / 0.2), transparent 40%);
}

The first layer in the list is painted on top. The background-color shows through wherever every layer is transparent. This technique gives you the "aurora" look that's popular on landing pages, without shipping a single image file.


Conic Gradients

A conic gradient sweeps colors around a center point, like the hands of a clock. Instead of changing with distance, color changes with angle.

.wheel {
  width: 200px;
  aspect-ratio: 1;
  border-radius: 50%;
  background-image: conic-gradient(red, yellow, lime, aqua, blue, magenta, red);
}

That's a color wheel in one declaration. Note that we repeat red at the end so the seam at the top blends smoothly.

from and at

You can rotate the starting angle with from and move the center with at:

.dial {
  background-image: conic-gradient(from -90deg at 50% 50%, #38bdf8, #818cf8);
}

Pie Charts with Hard Stops

Conic gradients with hard stops make perfect pie charts:

<div
  class="pie"
  role="img"
  aria-label="Traffic sources: 45% search, 30% social, 25% direct"
></div>
.pie {
  width: 220px;
  aspect-ratio: 1;
  border-radius: 50%;
  background-image: conic-gradient(
    #38bdf8 0% 45%,
    #f472b6 45% 75%,
    #fbbf24 75% 100%
  );
}

Add a radial-gradient layer on top, or a mask, to turn it into a donut:

.donut {
  width: 220px;
  aspect-ratio: 1;
  border-radius: 50%;
  background-image:
    radial-gradient(circle closest-side, #111827 60%, transparent 61%),
    conic-gradient(#38bdf8 0% 45%, #f472b6 45% 75%, #fbbf24 75% 100%);
}

Because these are just images, they're easy to drive from data with custom properties:

<div class="progress-ring" style="--value: 72"></div>
.progress-ring {
  --value: 0;
  width: 120px;
  aspect-ratio: 1;
  border-radius: 50%;
  background-image:
    radial-gradient(circle closest-side, #111827 78%, transparent 79%),
    conic-gradient(#34d399 calc(var(--value) * 1%), #334155 0);
}

A neat trick in that last stop: a position of 0 is automatically clamped to the previous stop's position, so it creates a hard stop right where the value ends.


Repeating Gradients

Each gradient type has a repeating version: repeating-linear-gradient(), repeating-radial-gradient(), and repeating-conic-gradient(). They repeat the stop pattern indefinitely, based on the distance between the first and last stop.

Stripes

.stripes {
  background-image: repeating-linear-gradient(
    45deg,
    #fbbf24 0 12px,
    #111827 12px 24px
  );
}

That gives you a "caution tape" pattern. For an animated barber-pole progress bar, it's easier to build one seamless tile with a regular linear-gradient and let background-size repeat it. Four equal bands at 45 degrees line up perfectly at the tile edges:

.progress {
  height: 12px;
  border-radius: 999px;
  background-color: #0ea5e9;
  background-image: linear-gradient(
    45deg,
    #38bdf8 25%,
    transparent 25% 50%,
    #38bdf8 50% 75%,
    transparent 75%
  );
  background-size: 24px 24px;
  animation: progress-move 0.8s linear infinite;
}

@keyframes progress-move {
  to {
    background-position: 24px 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .progress {
    animation: none;
  }
}

Because the animation moves the background by exactly one tile width, the loop is seamless, and the reduced-motion query stops it for people who've asked for less movement.

Checkerboards

Conic gradients make checkerboards trivial, which is great for showing transparency behind images:

.checker {
  background-image: repeating-conic-gradient(#334155 0 25%, #1f2937 0 50%);
  background-size: 24px 24px;
}

Interpolation and Color Spaces

By default, gradients interpolate in the sRGB color space for legacy colors. Blending two saturated colors through sRGB often produces a muddy, grayish middle. The classic example is blue to yellow, which passes through a dull gray.

Modern CSS lets you choose the interpolation space with the in keyword:

.srgb {
  background-image: linear-gradient(to right, blue, yellow);
}

.oklab {
  background-image: linear-gradient(to right in oklab, blue, yellow);
}

.oklch {
  background-image: linear-gradient(to right in oklch, blue, yellow);
}
  • in oklab gives a perceptually smooth blend without the muddy dip.
  • in oklch interpolates hue as well, so the middle passes through vivid colors. You can add shorter hue or longer hue to control which way around the wheel it goes.
.rainbow {
  background-image: linear-gradient(to right in oklch longer hue, red, red);
}

That single declaration draws a full rainbow, because longer hue forces the interpolation all the way around the hue wheel.

Color space interpolation is supported in all current major browsers. Older browsers throw out the entire declaration if they don't understand in oklch, so provide a fallback first:

.cta {
  background-image: linear-gradient(to right, #2563eb, #facc15);
  background-image: linear-gradient(to right in oklch, #2563eb, #facc15);
}

Animating Gradients

You can't directly transition between two gradient images. Changing background-image on hover simply swaps them instantly. There are a few reliable workarounds.

Animate background-position

Make the gradient larger than the element and slide it:

.button {
  padding: 0.75rem 1.5rem;
  border: 0;
  border-radius: 8px;
  color: #fff;
  background-image: linear-gradient(90deg, #38bdf8, #818cf8, #f472b6);
  background-size: 200% 100%;
  background-position: 0% 50%;
  transition: background-position 0.4s ease;
}

.button:hover {
  background-position: 100% 50%;
}

Animate Registered Custom Properties

With @property, the browser knows a custom property's type and can interpolate it. That lets you animate a color stop or an angle inside a gradient:

@property --angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

.spinner-border {
  border: 4px solid transparent;
  border-radius: 12px;
  background:
    linear-gradient(#111827, #111827) padding-box,
    conic-gradient(from var(--angle), #38bdf8, #818cf8, #f472b6, #38bdf8)
      border-box;
  animation: spin-border 3s linear infinite;
}

@keyframes spin-border {
  to {
    --angle: 360deg;
  }
}

This creates a rotating gradient border. The trick uses two background layers: a solid gradient clipped to the padding box and a conic gradient clipped to the border box, with a transparent border letting the conic layer show. @property is supported in all modern browsers. Where it isn't, the border simply stays static.


Common Pitfalls

  • Banding : Long, subtle gradients across large areas can show visible steps on some screens. Adding a middle stop, using in oklab, or layering a very faint noise texture helps.
  • Transparent in old engines : Very old browsers interpolated transparent as transparent black, creating gray fringes. Modern browsers use premultiplied alpha, so transparent is fine today, but when you need precise control, use the same color at zero alpha, like rgb(56 189 248 / 0).
  • Forgetting background-color : If a gradient fails to parse, the element may have no background at all. Always set a solid background-color fallback for text contrast.
  • Accessibility : Text over gradients must meet contrast requirements at every point. Check the lightest and darkest parts behind your text, not just the average.
  • Too many layers : Stacked full-page gradients are cheap, but animating many large ones can cost paint time. Profile with DevTools if an animation feels janky.

Conclusion

CSS gradients cover a remarkable amount of visual design with no image assets at all. Linear gradients handle overlays, fades, and stripes. Radial gradients give you spotlights and glows. Conic gradients unlock color wheels, pie charts, and progress rings. Their repeating variants turn into patterns, and modern color interpolation with in oklch fixes the muddy blends that used to make gradients look cheap.

Start by replacing one exported background image with a gradient, then experiment with hard stops and layered backgrounds. Once you think of gradients as images you can paint with code, you'll find uses for them everywhere.

Tags :
Share :

Related Posts

A Complete Guide to CSS Container Queries

A Complete Guide to CSS Container Queries

For more than a decade, responsive design meant one thing: media queries. You asked the browser how wide the viewport was and adjusted your layout ac

Continue Reading
A Comprehensive Guide to Installing Next.js

A Comprehensive Guide to Installing Next.js

Next.js has emerged as a powerful framework for building React applications, offering features like server-side rendering, static site generation, an

Continue Reading
Advanced CSS with clamp(), min(), and max(): Simplifying Dynamic Styling

Advanced CSS with clamp(), min(), and max(): Simplifying Dynamic Styling

CSS has evolved significantly, and modern tools like clamp(), min(), and max() are powerful game-changers in dynamic styling. If you’ve struggl

Continue Reading