Skip to main content

Harshal V. LADHE

Mastering CSS Gradients: Types, Use Cases & Best Practices

Design modern UI backgrounds using CSS gradients
Published at:
Last updated:
Estimated reading time:17 min read

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 right
  • 180deg → 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

CSS linear-gradient angle diagramFour identical two-color swatches, each showing the same gradient rotated to 0deg, 90deg, 180deg, and 270deg. 0deg points straight up, and the angle increases clockwise from there — 90deg points right, 180deg points down, and 270deg points left.0degto top90degto right180degto bottom270degto left

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: circle or ellipse
  • 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

radial-gradient circle vs ellipse diagramThe same radial gradient definition applied to a square box renders as a circle, and applied to a wide box renders as an ellipse — the shape keyword just picks which of the two the browser defaults to when it has to guess.circlesquare box → circleellipsewide box → ellipse (default)

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 box
  • closest-corner — stops at the nearest corner
  • farthest-side — stretches to the farthest edge
  • farthest-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

radial-gradient size keyword diagramA square box with an off-center gradient origin, showing the 4 size keywords as complete boundary circles measured from that origin: closest-side and closest-corner stop at the nearest edge or corner, while farthest-side and farthest-corner reach the farthest one, extending well past the box itself — a real background gradient would still only ever paint inside the box, but every boundary is drawn in full here so its true size is never hidden.gradient origin (25% 30%)closest-side90pxclosest-corner142pxfarthest-side230pxfarthest-corner311px

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 angle
  • at 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

conic-gradient sweep diagramA color wheel that sweeps clockwise starting from 0deg at the top (12 o'clock), around through 90deg on the right, 180deg at the bottom, and 270deg on the left, back to the start — centered on the gradient's "at" position.
from 0degsweeps clockwise90deg180deg270degat 50% 50% — the gradient's center point

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, and conic gradients
  • 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

repeating-gradient repeat-unit diagramA striped bar where the first 20px tile — the span between the gradient's first and last color stop — is highlighted as the "repeat unit," then tiles infinitely across the rest of the bar. The same idea works outward instead of sideways for repeating-radial-gradient.repeat unit — 20px0% → 100% of the color stops, then it starts overthe same idea, radiating outwardrepeat unit — 20px radiusrings repeat outward every 20px

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

gradient layering z-order diagramThree background layers stacked like offset cards — a light reflection, an ambient highlight glow, and the backdrop color. The layer listed first in the CSS renders on top, and all three composite together into the final glass-card effect shown on the right. light reflection — linear ambient highlight — radial backdrop color — base, no gradient=what you actually seefirst-listed layer = rendered on top

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 (or in 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-image directly. Instead, animate opacity or transform on a pseudo-element (::before or ::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>

Ln , Col HTML14.5 KBUTF-8

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 userSpaceOnUse vs. 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. 🌈