
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 :
circleorellipse. - Size : An explicit length such as
200px, or one of four keywords:closest-side,closest-corner,farthest-side, andfarthest-corner(the default). - Position : After
at, using the same syntax asbackground-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 oklabgives a perceptually smooth blend without the muddy dip.in oklchinterpolates hue as well, so the middle passes through vivid colors. You can addshorter hueorlonger hueto 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
transparentas transparent black, creating gray fringes. Modern browsers use premultiplied alpha, sotransparentis fine today, but when you need precise control, use the same color at zero alpha, likergb(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-colorfallback 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.


