Mastering CSS Gradients: Types, Use Cases & Best Practices
Gradients are one of the most powerful design tools in CSS. They allow you to blend colors smoothly without images, making your site faster, more scalable, and visually appealing. But with several types of gradients and endless combinations, knowing when and how to use them is key.
If you've never written one before, that sentence is the whole trick. Every gradient you'll see in this post is really
just background-image with a formula instead of a file. No download, no <img> tag, nothing to export from a design
tool — you describe a transition in CSS, and the browser renders it.
In this post, we'll cover:
- What gradients are and why they matter
- Different types of CSS gradients
- Practical use cases for each
- SCSS helpers to simplify your workflow
- Animated gradient demos
- Best practices for performance and design
What is a CSS Gradient?
A CSS gradient is a background image that smoothly transitions between two or more colors. Unlike raster images, gradients are generated by the browser, so they are:
- Scalable (no resolution loss)
- Lightweight (no HTTP requests for images)
- Customizable (dynamic colors, angles, shapes)
Here's the part that trips up a lot of beginners: a gradient is a value for background-image (or the background
shorthand), not a value for color or background-color. That's why you'll never see color: linear-gradient(...) —
colors are single, flat values, but a gradient is a whole image, generated in place of a photo or an icon. Once that
clicks, a lot of gradient syntax stops feeling arbitrary: it behaves like any other background image, which means it
can be sized, positioned, repeated, and even layered with other background images — all things a plain color can't do.
Types of CSS Gradients
CSS gives you four gradient functions, and each one answers a different question about how the colors should flow: in a straight line, outward from a point, around a point, or endlessly. Once you know which question you're asking, picking the right function becomes automatic.
Linear Gradient
Colors transition along a straight line (horizontal, vertical, diagonal, or custom angle).
.linear-gradient {
background: linear-gradient(90deg, #fb923c, #e11d48);
}90deg→ left to right180deg→ top to bottom- Named directions also work:
to right,to bottom
The angle is the one detail that surprises almost everyone coming to CSS from math class, a design tool, or even SVG:
0deg points straight up, and the angle increases clockwise from there. So 90deg is "to the right," 180deg
is "to the bottom," and 270deg is "to the left" — a full lap back to where you started at 360deg. If you'd rather
not do the mental math, the keyword syntax (to right, to bottom left, and so on) says exactly what it means and
skips the angle entirely.
Visual Illustration
You can also add as many color stops as you like, and each stop can be pinned to a specific position instead of being spaced out evenly:
.sunset {
/* 3 stops instead of 2 — the browser blends smoothly between each pair */
background: linear-gradient(180deg, #1e3c72 0%, #fb923c 60%, #e11d48 100%);
}Use Cases:
Backgrounds, buttons, borders, text effects
Radial Gradient
Colors radiate outward from a center point, forming circles or ellipses.
.radial-gradient {
background: radial-gradient(circle at center, #10b981, #0ea5e9);
}- Shape:
circleorellipse - Position:
at center,at top left, etc.
A radial gradient always starts from one origin point (at ...) and spreads outward in every direction from there,
which is why it's the natural choice whenever you want to draw attention to a specific spot instead of across a
surface — a spotlight, a glow, a highlight. Left unspecified, the shape defaults to whichever matches the box it's
painted on: a square box gets a circle, and anything wider or taller than it is tall gets an ellipse.
Visual Illustration
The part that trips people up isn't the shape — it's how far the gradient reaches before it finishes, which is controlled by an optional size keyword you can place right after the shape:
closest-side— stops at the nearest edge of the boxclosest-corner— stops at the nearest cornerfarthest-side— stretches to the farthest edgefarthest-corner— stretches to the farthest corner (the default if you don't specify one)
These only visibly differ from each other when the gradient's center isn't dead-center in the box — which, in real layouts (buttons, cards, hero sections), is most of the time.
Visual Illustration
Use Cases:
Spotlight effects, glowing buttons, illustrations
Conic Gradient
Colors rotate around a center point, like a pie chart.
.conic-gradient {
background: conic-gradient(from 90deg at 50% 50%, #8b5cf6, #f59e0b);
}from angle→ starting rotation angleat x y→ gradient center position
Of the four gradient types, conic is usually the least familiar — most people have written a background color, plenty
have written a linear gradient, but a gradient that sweeps around a point like a clock hand is a genuinely new shape
to think in. The mental model is simpler than the syntax suggests: pick a center (at), pick a starting angle
(from — using the exact same clockwise-from-the-top convention as linear gradients), and the colors rotate around
that point exactly once, in order, back to where they started.
Visual Illustration
Because the colors sweep around a full circle instead of flowing in one direction, conic gradients are the only type that can produce hard-edged pie slices and color wheels just by placing stops at the same position:
.pie-chart {
/* Repeating the position (25%) between two colors creates a hard edge instead of a blend */
background: conic-gradient(#4facfe 0% 25%, #00f2fe 25% 60%, #fbc2eb 60% 100%);
}Use Cases:
Pie charts, color wheels, progress indicators
Repeating Gradients
Repeats the gradient infinitely.
.repeating-linear-gradient {
background: repeating-linear-gradient(45deg, #4338ca, #4338ca 10px, #22d3ee 10px, #22d3ee 20px);
}- Works with
linear,radial, andconicgradients - Define stops carefully for stripes, patterns, and textures
The key to reading a repeating gradient is realizing there's nothing magic about the word "repeating" itself — it
just tells the browser to treat the distance between your first and last color stop as one tile, and then
copy that tile forever. In the example above, the stops run from 0 to 20px, so that 20px slice — 10px of #4338ca
followed by 10px of #22d3ee — is the repeat unit, and everything past it is just more copies of the same tile.
Visual Illustration
The same idea works just as well radiating outward instead of running sideways:
.repeating-radial-gradient {
/* Same repeat-unit idea as above, just measured as distance from the center instead of along an axis */
background: repeating-radial-gradient(circle at center, #4338ca, #4338ca 10px, #22d3ee 10px, #22d3ee 20px);
}Use Cases:
Stripes, checkerboards, subtle textures
Advanced CSS Gradient Concepts
The four gradient types cover what you can draw. The next few techniques cover how well it looks once you draw it — smoother transitions, more accurate colors, and more depth than a single flat gradient can offer on its own.
Interpolation Hints (The Midpoint)
Most developers define color stops, but they forget the "hint." By placing a percentage between two colors, you control the midpoint of the transition.
.gradient {
/* The transition midpoint is at 20% instead of the default 50% */
background: linear-gradient(90deg, #fb923c, 20%, #e11d48);
}By default, a two-color gradient blends most evenly right at the halfway point between its stops — the color at 50% is a perfect 50/50 mix of the two. A hint overrides that: it's a bare percentage with no color attached, and it tells the browser "the 50/50 blend point should happen here instead." Push the hint toward one end and that color dominates more of the gradient; push it toward the other and the balance flips. This is crucial for creating "sharp" or "weighted" transitions that feel more organic instead of mechanically even.
You can experiment with this live — drag the midpoint slider in the Interactive Playground further down this post to see the effect in real time before moving on.
Modern Color Spaces: OKLCH and the "Gray Dead Zone"
When gradients are created using traditional RGB or HEX colors, transitions between two vibrant hues (for example, blue → yellow) often pass through a dull, grayish midpoint. This visually unpleasant artifact is commonly called the "gray dead zone." It happens because RGB interpolates color values mathematically, not perceptually—human vision does not perceive brightness and saturation linearly in RGB space.
The Solution: OKLCH
OKLCH is a perceptual color space designed to keep lightness (L) and chroma (C) consistent across transitions. As a result, gradients remain vivid and balanced throughout their entire range, without muddy midpoints.
RGB vs OKLCH Gradient Comparison
/* RGB (can look muddy in the middle) */
.muddy-gradient {
background: linear-gradient(to right, blue, yellow);
}
/* OKLCH (perceptually vibrant and clean) */
.vibrant-gradient {
background: linear-gradient(in oklch to right, blue, yellow);
}Explicit OKLCH Control (Recommended)
For maximum consistency and design control, define your colors directly in OKLCH:
.gradient {
/* A vibrant, perceptually uniform gradient */
background: linear-gradient(to right, oklch(70% 0.2 30), oklch(70% 0.2 290));
}Why This Matters
- No gray dead zones: Smooth, saturated transitions end-to-end
- Predictable brightness: Lightness stays visually consistent
- Better design intent: What you design is what users perceive
You can toggle OKLCH interpolation on and off live in the Interactive Playground below to watch a muddy RGB transition snap into a vivid one — seeing the difference side-by-side makes it click faster than any amount of reading about it.
Layered Gradients: The "Glassmorphism" Secret
High-end UI design rarely relies on a single gradient. The real depth comes from layering multiple gradients, each simulating how light interacts with a surface. This technique is at the core of Glassmorphism, where subtle highlights, soft shadows, and blurred backgrounds create a translucent, frosted-glass effect.
By stacking linear and radial gradients, you can mimic directional light, ambient glow, and color bleed—without images.
Here's the part that isn't obvious the first time you see it: background accepts a comma-separated list of
images, and when several are given, CSS stacks them like layers in a design tool — but with one rule that catches
almost everyone off guard: the first one listed renders on top, not the last. It reads top-to-bottom in the CSS,
and it paints top-to-bottom on screen too.
Visual Illustration
Why Layered Gradients Work:
- Depth illusion: Multiple light sources feel more realistic
- Mesh-like complexity: Radial gradients can simulate mesh gradients
- Lightweight: Pure CSS, no images or SVGs required
- Highly customizable: Adjust opacity, position, and blend easily
Glassmorphism with Layered Gradients
.glass-card {
background:
/* Light reflection */
linear-gradient(120deg, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.05)),
/* Ambient highlights */
radial-gradient(at 0% 0%, rgba(255, 255, 255, 0.15) 0%, transparent 50%),
radial-gradient(at 50% 0%, hsla(347, 60%, 35%, 0.35) 0%, transparent 50%),
radial-gradient(at 100% 0%, hsla(339, 49%, 30%, 0.35) 0%, transparent 50%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 16px;
}Design Tips:
- Use linear gradients for directional light reflections
- Use radial gradients for glow, highlights, or mesh-like depth
- Keep opacities low (0.05–0.35) for a premium feel
- Pair with
backdrop-filter: blur()to complete the glass effect
Layered gradients transform flat surfaces into visually rich components, making them an essential technique for modern, polished UI systems.
Toggle the ① and ② layers on and off in the Layering tab of the Interactive Playground below to feel the first-listed-renders-on-top rule directly, instead of just reading about it.
SCSS Gradient Utilities (Modern, DRY, and Future-Proof)
To keep gradient usage consistent, maintainable, and future-ready, you can combine utility helpers with modern color-space safety into a single SCSS utility layer. This approach gives you clean syntax, avoids repetition, and ensures perceptually correct gradients where supported—while still providing reliable fallbacks.
If you haven't written a Sass @mixin before, think of it as a function for CSS: you define it once with some
parameters, and every place you @include it drops in the fully-expanded CSS for those specific arguments — so you
get to write @include gradient(linear, to right, ...) everywhere instead of retyping the same @supports block and
linear-gradient(...) in oklch boilerplate on every single class.
Unified SCSS Gradient Mixin
This mixin supports linear, radial, and conic gradients, includes a fallback for older browsers, and automatically upgrades to the OKLCH color space when available.
@mixin gradient($type: linear, $direction: 90deg, $position: center, $shape: ellipse, $size: farthest-corner, $colors...) {
@if $type == linear {
background: linear-gradient($direction, $colors...);
} @else if $type == radial {
background: radial-gradient(#{$shape} #{$size} at #{$position}, $colors...);
} @else if $type == conic {
background: conic-gradient(from $direction at $position, $colors...);
}
// Modern color-space upgrade
@supports (color: oklch(0% 0 0)) {
@if $type == linear {
background: linear-gradient($direction, $colors...) in oklch;
} @else if $type == radial {
background: radial-gradient(#{$shape} #{$size} at #{$position}, $colors...) in oklch;
} @else if $type == conic {
background: conic-gradient(from $direction at $position, $colors...) in oklch;
}
}
}Usage Examples
.card {
@include gradient(linear, to right, center, ellipse, farthest-corner, oklch(60% 0.15 300), oklch(60% 0.15 30));
}
.avatar {
@include gradient(radial, 0deg, top, ellipse, farthest-side, #ff9a9e, #fad0c4);
}
.profile-badge {
@include gradient(radial, 0deg, center, circle, closest-side, #43cea2, #185a9d);
}
.hero-bg {
@include gradient(radial, 0deg, center, ellipse, 70% 40%, #ff512f, #dd2476);
}
.loader {
@include gradient(conic, 0deg, center, ellipse, farthest-corner, #4facfe, #00f2fe, #4facfe);
}Compiled CSS Output:
.card {
background: linear-gradient(to right, oklch(60% 0.15 300), oklch(60% 0.15 30));
}
@supports (color: oklch(0% 0 0)) {
.card {
background: linear-gradient(to right, oklch(60% 0.15 300), oklch(60% 0.15 30)) in oklch;
}
}
.avatar {
background: radial-gradient(ellipse farthest-side at top, #ff9a9e, #fad0c4);
}
@supports (color: oklch(0% 0 0)) {
.avatar {
background: radial-gradient(ellipse farthest-side at top, #ff9a9e, #fad0c4) in oklch;
}
}
.profile-badge {
background: radial-gradient(circle closest-side at center, #43cea2, #185a9d);
}
@supports (color: oklch(0% 0 0)) {
.profile-badge {
background: radial-gradient(circle closest-side at center, #43cea2, #185a9d) in oklch;
}
}
.hero-bg {
background: radial-gradient(ellipse 70% 40% at center, #ff512f, #dd2476);
}
@supports (color: oklch(0% 0 0)) {
.hero-bg {
background: radial-gradient(ellipse 70% 40% at center, #ff512f, #dd2476) in oklch;
}
}
.loader {
background: conic-gradient(from 0deg at center, #4facfe, #00f2fe, #4facfe);
}
@supports (color: oklch(0% 0 0)) {
.loader {
background: conic-gradient(from 0deg at center, #4facfe, #00f2fe, #4facfe) in oklch;
}
}Animated Gradient Demos
Gradients aren't just static backgrounds—they can be the engine for high-end UI animations. Because browsers historically struggled to animate color values directly, we use clever tricks with positioning, rotation, and masking to create movement.
None of the three demos below are essential to reading the content around them, which makes them exactly the kind
of motion prefers-reduced-motion exists for:
@media (prefers-reduced-motion: reduce) {
.animated-bg,
.border-beam::before,
.sweep-loader {
animation: none;
}
}Pair this with every animated gradient you ship — it costs one media query and respects a setting some visitors have turned on specifically because motion like this causes them discomfort.
All three demos below are also live in the Animated Demos tab of the Interactive Playground, duration slider and reduced-motion simulator included.
Background Position Trick (Liquid Flow)
To bring a site to life, you can animate the background-position of a gradient. Because browsers can't easily animate the
colors themselves, we make the gradient larger than the container and move it.
.animated-bg {
/* High-contrast colors work best for this effect */
background: linear-gradient(270deg, #ff9a9e, #fad0c4, #fbc2eb, #a6c1ee);
background-size: 600% 600%;
animation: gradient-flow 8s ease infinite;
}
@keyframes gradient-flow {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}The trick has two parts working together: background-size: 600% 600% blows the gradient up to 6x the size of its
element, so only a small window of it is ever visible at once — then the @keyframes slide that window around by
changing background-position. The colors themselves never move or change; you're just panning the "camera" over a
much bigger gradient than you can see.
The "Border Beam" (Conic Perimeter)
A massive trend in SaaS design is the glowing border that travels around a card. This is achieved by rotating a
conic-gradient behind the card content. The "beam" is simply a small slice of color in an otherwise transparent
or dark gradient.
.border-beam {
position: relative;
background: #1a1a2e; /* Card inner color */
overflow: hidden;
}
.border-beam::before {
content: '';
position: absolute;
inset: -200%; /* Make it much larger than the card */
background: conic-gradient(
from 0deg,
transparent 0%,
transparent 80%,
#4facfe 90%, /* The "beam" color */
transparent 100%
);
animation: spin 4s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}Use Cases:
Same idea as the sweep in the Conic Gradient section above, just spun continuously — the beam
is a mostly-transparent conic gradient, so transform: rotate() on the whole pseudo-element makes it look like a
single bright slice is orbiting the card, without ever touching the gradient's own color stops.
The "Loading Sweep" Spinner
Instead of a simple rotating circle, you can create a high-end "sweep" loader by transitioning from a solid color to transparency within a conic gradient.
.sweep-loader {
width: 50px;
height: 50px;
border-radius: 50%;
/* Fades from blue to transparent */
background: conic-gradient(from 0deg, #2575fc, transparent);
/* Use a mask to make it a ring instead of a solid circle */
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #fff 0);
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}The mask line is doing more work than it looks: a solid conic-gradient circle, on its own, is just a filled disc.
The radial-gradient mask punches the middle out of it — transparent for the inner radius, opaque for a thin 5px
band at the edge — turning the filled disc into a ring, entirely with CSS and no image asset.
Best Practices for Gradients
- Use modern color spaces: Write gradients using the
in oklch(orin lch) syntax to avoid muddy mid-tones and the common "gray zone" seen in RGB interpolation. - Maintain accessibility (A11y): Always ensure text placed over gradients meets WCAG 2.1 contrast ratios. Verify contrast using tools like Whocanuse or browser dev tools.
- Preserve readability: Avoid placing critical text on busy gradients. If using
background-clip: text, test across themes and screen types. - Optimize animation performance: Never animate
background-imagedirectly. Instead, animateopacityortransformon a pseudo-element (::beforeor::after) to maintain smooth 60fps rendering. - Use gradients sparingly: Gradients draw attention—overusing them can feel noisy and unprofessional.
- Prefer subtle transitions: Soft, low-contrast gradients generally age better than harsh or rainbow-heavy combinations.
- Provide fallbacks: Older browsers may not support
conic-gradient()or modern color spaces. Always define a solid-color or linear-gradient fallback.
When to Use Which Gradient?
- Linear: Buttons, hero backgrounds, separators
- Radial: Focus effects, glowing highlights, spotlight designs
- Conic: Data visualizations, progress indicators
- Repeating: Stripes, patterns, textures
Interactive Playground
Reading about gradient types and color spaces is one thing, but tuning them by hand is another. The playground below has three
tabs: Gradient Types switches between linear, radial, conic, and repeating gradients, drags the midpoint off 50% to feel
what an interpolation hint does, and toggles OKLCH interpolation to watch a muddy RGB transition turn vivid; Layering lets
you toggle the glass-card's gradient layers on and off to feel the z-order rule from earlier; and Animated Demos runs the
liquid-flow, border-beam, and loading-sweep techniques live, with a duration slider and a prefers-reduced-motion simulator.
CSS Gradients Playground
Switch between linear, radial, conic, and repeating gradients, stack a live glassmorphism card, and preview all three animated gradient techniques from the post.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>CSS Gradients Playground</title> </head> <body> <main class="playground"> <header class="playground-header"> <h1>CSS Gradients Playground</h1> <p>Switch between gradient types, stack layers into a glass card, and preview all three animated techniques from the post — all with the generated CSS shown live.</p> </header> <div class="tabs" role="tablist" aria-label="Playground mode" id="mode-switch"> <button class="tab tab--active" type="button" data-mode="types" role="tab" aria-selected="true">Gradient Types</button> <button class="tab" type="button" data-mode="layering" role="tab" aria-selected="false">Layering</button> <button class="tab" type="button" data-mode="animated" role="tab" aria-selected="false">Animated Demos</button> </div> <div id="types-demo" class="tab-panel tab-panel--active"> <section class="demo-area" aria-label="Gradient preview"> <div id="gradient-preview" class="gradient-preview"></div> </section> <output id="gradient-output" class="readout" aria-live="polite"></output> <fieldset class="control-group"> <legend>Gradient Type</legend> <div class="segmented-control" role="radiogroup" aria-label="Gradient type"> <label class="segment"> <input type="radio" name="gradient-type" value="linear" checked> <span>Linear</span> </label> <label class="segment"> <input type="radio" name="gradient-type" value="radial"> <span>Radial</span> </label> <label class="segment"> <input type="radio" name="gradient-type" value="conic"> <span>Conic</span> </label> <label class="segment"> <input type="radio" name="gradient-type" value="repeating"> <span>Repeating</span> </label> </div> <div class="fields-grid"> <div id="type-options-container"> <div id="linear-options" class="type-group active-group"> <div class="field"> <label for="angle-input">Angle</label> <div class="range-row"> <span class="range-edge">0deg</span> <div class="range-track"> <input type="range" id="angle-input" min="0" max="360" step="1" value="90"> <output class="range-bubble" id="angle-value" for="angle-input">90deg</output> </div> <span class="range-edge">360deg</span> </div> </div> </div> <div id="radial-options" class="type-group"> <div class="fields-grid"> <div class="field"> <label for="shape-select">Shape</label> <select id="shape-select"> <option value="circle">circle</option> <option value="ellipse" selected>ellipse</option> </select> </div> <div class="field"> <label for="radial-position-select">Position</label> <select id="radial-position-select"> <option value="center" selected>center</option> <option value="top">top</option> <option value="bottom">bottom</option> <option value="left">left</option> <option value="right">right</option> <option value="top left">top left</option> <option value="top right">top right</option> <option value="bottom left">bottom left</option> <option value="bottom right">bottom right</option> </select> </div> </div> </div> <div id="conic-options" class="type-group"> <div class="fields-grid"> <div class="field"> <label for="from-angle-input">From angle</label> <div class="range-row"> <span class="range-edge">0deg</span> <div class="range-track"> <input type="range" id="from-angle-input" min="0" max="360" step="1" value="0"> <output class="range-bubble" id="from-angle-value" for="from-angle-input">0deg</output> </div> <span class="range-edge">360deg</span> </div> </div> <div class="field"> <label for="conic-position-select">Position</label> <select id="conic-position-select"> <option value="center" selected>center</option> <option value="top">top</option> <option value="bottom">bottom</option> <option value="left">left</option> <option value="right">right</option> <option value="top left">top left</option> <option value="top right">top right</option> <option value="bottom left">bottom left</option> <option value="bottom right">bottom right</option> </select> </div> </div> </div> <div id="repeating-options" class="type-group"> <div class="fields-grid"> <div class="field"> <label for="repeat-shape-select">Shape</label> <select id="repeat-shape-select"> <option value="linear" selected>linear (stripes)</option> <option value="radial">radial (rings)</option> </select> </div> <div class="field" id="repeat-angle-field"> <label for="repeat-angle-input">Angle</label> <div class="range-row"> <span class="range-edge">0deg</span> <div class="range-track"> <input type="range" id="repeat-angle-input" min="0" max="360" step="1" value="45"> <output class="range-bubble" id="repeat-angle-value" for="repeat-angle-input">45deg</output> </div> <span class="range-edge">360deg</span> </div> </div> </div> <div class="field"> <label for="repeat-width-input">Repeat unit width</label> <div class="range-row"> <span class="range-edge">4px</span> <div class="range-track"> <input type="range" id="repeat-width-input" min="4" max="60" step="2" value="20"> <output class="range-bubble" id="repeat-width-value" for="repeat-width-input">20px</output> </div> <span class="range-edge">60px</span> </div> </div> <p class="tip">Repeating gradients use hard-edged stripes, not a smooth blend, so only the Start and End colors below are used — the midpoint color and slider are ignored.</p> </div> </div> </div> </fieldset> <fieldset class="control-group"> <legend>Color Stops</legend> <div class="fields-grid fields-grid--3"> <div class="field"> <label for="color-start" id="color-start-label">Start (0%)</label> <input type="color" id="color-start" value="#f97316"> </div> <div class="field" id="color-mid-field"> <label for="color-mid">Midpoint</label> <input type="color" id="color-mid" value="#14b8a6"> </div> <div class="field" id="color-end-field"> <label for="color-end" id="color-end-label">End (100%)</label> <input type="color" id="color-end" value="#a855f7"> </div> <div class="field field--full" id="midpoint-field"> <label for="midpoint-input">Midpoint position</label> <div class="range-row"> <span class="range-edge">0%</span> <div class="range-track"> <input type="range" id="midpoint-input" min="0" max="100" step="1" value="50"> <output class="range-bubble" id="midpoint-value" for="midpoint-input">50%</output> </div> <span class="range-edge">100%</span> </div> </div> </div> <p class="tip" id="midpoint-tip">Drag the midpoint off 50% to see how an interpolation hint shifts where the color transition happens.</p> </fieldset> <fieldset class="control-group"> <legend>Color Space</legend> <div class="fields-grid"> <label class="checkbox-field" for="oklch-toggle"> <input type="checkbox" id="oklch-toggle"> <span>Interpolate in OKLCH (<code>in oklch</code>)</span> </label> </div> <p class="tip">Toggle this on a blue → yellow gradient to see the muddy RGB midpoint disappear.</p> </fieldset> </div> <div id="layering-demo" class="tab-panel"> <section class="demo-area glass-demo-area" aria-label="Glassmorphism layer preview"> <div class="glass-backdrop"></div> <div id="glass-card" class="glass-card"></div> </section> <output id="glass-output" class="readout" aria-live="polite"></output> <fieldset class="control-group"> <legend>Layers (top → bottom)</legend> <div class="fields-grid"> <div class="field field--full"> <label class="checkbox-field layer-toggle" for="layer-sheen-toggle"> <input type="checkbox" id="layer-sheen-toggle" checked> <span>① Light reflection — <code>linear-gradient</code></span> </label> </div> <div class="field field--full"> <label class="checkbox-field layer-toggle" for="layer-glow-toggle"> <input type="checkbox" id="layer-glow-toggle" checked> <span>② Color glow — <code>radial-gradient</code></span> <input type="color" id="glow-color" value="#f472b6" aria-label="Glow color"> </label> </div> </div> <p class="tip">Uncheck ① and the reflection disappears from the top of the stack first — that's the same first-listed-renders-on-top rule from the diagram above, live.</p> </fieldset> <fieldset class="control-group"> <legend>Glass Effect</legend> <div class="fields-grid"> <div class="field"> <label for="blur-input">Backdrop blur</label> <div class="range-row"> <span class="range-edge">0px</span> <div class="range-track"> <input type="range" id="blur-input" min="0" max="24" step="1" value="10"> <output class="range-bubble" id="blur-value" for="blur-input">10px</output> </div> <span class="range-edge">24px</span> </div> </div> <div class="field"> <label for="opacity-input">Layer intensity</label> <div class="range-row"> <span class="range-edge">0%</span> <div class="range-track"> <input type="range" id="opacity-input" min="0" max="200" step="10" value="100"> <output class="range-bubble" id="opacity-value" for="opacity-input">100%</output> </div> <span class="range-edge">200%</span> </div> </div> </div> <p class="tip">The striped pattern behind the card only exists so <code>backdrop-filter: blur()</code> has something to blur — drag it to 0px to see the card go flat.</p> </fieldset> </div> <div id="animated-demo" class="tab-panel"> <div class="segmented-control" role="radiogroup" aria-label="Animated demo"> <label class="segment"> <input type="radio" name="anim-demo" value="liquid" checked> <span>Liquid Flow</span> </label> <label class="segment"> <input type="radio" name="anim-demo" value="beam"> <span>Border Beam</span> </label> <label class="segment"> <input type="radio" name="anim-demo" value="sweep"> <span>Loading Sweep</span> </label> </div> <section class="demo-area anim-demo-area" aria-label="Animated gradient preview"> <div id="liquid-flow-el" class="anim-el liquid-flow active-anim"></div> <div id="border-beam-el" class="anim-el border-beam"><span>Card content</span></div> <div id="sweep-loader-el" class="anim-el sweep-loader"></div> </section> <fieldset class="control-group"> <legend>Animation</legend> <div class="fields-grid"> <div class="field field--full"> <label for="speed-input">Duration</label> <div class="range-row"> <span class="range-edge">1s</span> <div class="range-track"> <input type="range" id="speed-input" min="1" max="15" step="1" value="8"> <output class="range-bubble" id="speed-value" for="speed-input">8s</output> </div> <span class="range-edge">15s</span> </div> </div> <div class="field field--full"> <label class="checkbox-field" for="reduced-motion-toggle"> <input type="checkbox" id="reduced-motion-toggle"> <span>Simulate <code>prefers-reduced-motion: reduce</code></span> </label> </div> </div> <p class="tip" id="anim-tip">Only <code>background-position</code> is animating here — background-size: 600% 600% makes the gradient big enough that panning it looks like flowing liquid.</p> </fieldset> </div> </main> <script src="./index.js"></script> </body> </html>
Starting sandbox…
No console output yet.
No original version of /index.html to compare against.
Conclusion
Gradients are more than decoration—they bridge flat design and depth when used thoughtfully. By mastering linear, radial, conic, and repeating gradients, and by leveraging modern color spaces like OKLCH for perceptual consistency, you can create interfaces that feel modern, polished, and visually engaging.
With the right balance of subtlety, accessibility, and performance-aware techniques—combined with reusable SCSS helpers and animation patterns—gradients become a powerful, scalable design tool that enhances UI quality without sacrificing maintainability or user experience.
What's Next: Taking Gradients to the Vector Level
Mastering CSS gradients is a huge step for any UI developer, but there are times when CSS reaches its limits. If you need a gradient to follow a complex vector path, animate specific color stops with high precision, or apply a gradient to a custom-shaped icon, you need SVG Gradients.
In our upcoming deep dive for the SVG category, we will explore:
- The
<linearGradient>and<radialGradient>tags: Defining gradients in the DOM. - Coordinate Systems: Mastering
userSpaceOnUsevs.objectBoundingBox. - SVG-Specific Effects: Using spread methods like reflect and repeat for complex patterns.
- Vector Precision: Applying gradients to paths, masks, and text filters.
Our comprehensive guide to SVG Gradients is currently in the works—stay tuned to the SVG category for the update!
The five diagrams gave me the most trouble up front—getting the off-center radial boundaries to line up with the actual math, and the conic sweep to read as clockwise-from-the-top at a glance—and turning the playground into three real tabs (gradient types plus repeating, a toggleable glass layer stack, and all three animated demos with a reduced-motion switch) took a lot more wiring than the original single-panel version. I hope dragging every one of those controls makes gradients click in a way reading about them never quite does. Thanks for gradient-ing along with me. 🌈