A Complete Guide to CSS linear-gradient()

linear-gradient() is the workhorse behind modern backgrounds, overlays, borders, and even text effects. You will learn the syntax deeply, control angles and hard stops precisely, layer multiple gradients, create crisp stripes with repeating-linear-gradient(), and animate gradients without images. By the end, you will be able to paint polished UI components with a few lines of CSS.

Why linear-gradient() Matters

linear-gradient() renders sharp, resolution-independent color transitions in the browser. It removes extra network requests for background images, scales cleanly on any screen, and responds to theming with a variable change. You can layer several gradients to build depth, create separator lines, or fake borders. Gradients interpolate colors without aliasing, and you can use transparency to overlay gradients on photos or solid fills. The function is widely supported and works anywhere a background-image is accepted, and also inside masks and borders.

Prerequisites

You do not need a design tool for this guide. You will work with angles, color stops, and layering directly in CSS. A small set of variables will keep the examples tidy and themeable.

  • Basic HTML
  • CSS custom properties
  • CSS pseudo-elements (::before / ::after)

Step 1: The HTML Structure

The demo uses a hero section, a content grid with a card, a gradient-bordered box, a call-to-action button, a divider, and a gradient text label. This is all the markup you need, and the rest of the work happens in CSS.

<!-- HTML -->
<header class="hero">
  <h1>Master linear-gradient()</h1>
  <p>Angles, hard stops, layers, and animations, all in pure CSS.</p>
  <button class="btn">Primary Action</button>
</header>

<section class="grid">
  <article class="card">
    <h2>Gradient Card</h2>
    <p>A soft background wash that stays crisp on any screen.</p>
  </article>

  <div class="border-gradient-box">
    <h3>Gradient Border Box</h3>
    <p>No images. A dual-layer background simulates the border.</p>
  </div>
</section>

<div class="divider" aria-hidden="true"></div>

<p class="kicker text-gradient">Live gradient text</p>

Step 2: The Basic CSS & Styling

This base layer sets up type, spacing, and theme variables. It also establishes a neutral canvas so the gradients stand out. The CSS variables hold brand hues and neutral colors. You will reference them in each component.

/* CSS */
:root {
  --brand-1: hsl(260 80% 60%);
  --brand-2: hsl(280 85% 58%);
  --brand-3: hsl(310 90% 56%);
  --brand-4: hsl(340 85% 60%);
  --ink: hsl(220 25% 15%);
  --muted-ink: hsl(220 10% 40%);
  --paper: hsl(0 0% 100%);
  --panel: hsl(220 20% 98%);
  --shadow: hsl(220 20% 10% / 0.12);
}

*,
*::before,
*::after { box-sizing: border-box; }

body {
  margin: 0;
  font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, Apple Color Emoji, Segoe UI Emoji;
  color: var(--ink);
  background: var(--panel);
  line-height: 1.5;
}

.hero,
.grid,
.divider,
.kicker { margin: 0 auto; max-width: 72rem; padding-inline: 1rem; }

.hero {
  padding-block: 6rem 5rem;
  border-radius: 1rem;
  box-shadow: 0 8px 24px var(--shadow);
  color: white;
  text-align: center;
}

.hero h1 { margin: 0 0 0.5rem; font-size: 2.5rem; }
.hero p { margin: 0 0 1.25rem; color: hsl(0 0% 100% / 0.9); }

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1.25rem;
  padding-block: 2rem 0;
}

.card {
  padding: 1.25rem;
  border-radius: 0.75rem;
  background: var(--paper);
  box-shadow: 0 4px 16px var(--shadow);
}

.border-gradient-box {
  padding: 1.25rem;
  border-radius: 0.75rem;
  background: var(--paper);
}

.btn {
  border: 0;
  border-radius: 999px;
  padding: 0.75rem 1.25rem;
  color: white;
  cursor: pointer;
  font-weight: 600;
}

.divider {
  height: 12px;
  margin-block: 3rem 2rem;
  border-radius: 999px;
}

.kicker {
  text-align: center;
  font-weight: 700;
  letter-spacing: 0.02em;
  margin-bottom: 4rem;
}

Advanced Tip: Declare gradients with CSS variables when you reuse the same color stops across components. A single theme switch can then recolor hero backgrounds, borders, and buttons at once.

Step 3: Building the Hero Overlay Gradient

The hero uses a three-stop diagonal gradient with a subtle highlight band. The highlight comes from a second gradient layer that fades out across the banner. Angles, stops, and transparency do the heavy lifting.

/* CSS */
.hero {
  /* base visuals are in Step 2; now add gradients */
  background-image:
    /* highlight band */
    linear-gradient( to right,
      hsl(0 0% 100% / 0.22) 0%,
      hsl(0 0% 100% / 0.08) 30%,
      hsl(0 0% 100% / 0) 60%
    ),
    /* main diagonal color wash */
    linear-gradient( 135deg,
      var(--brand-1) 0%,
      var(--brand-2) 45%,
      var(--brand-3) 100%
    );
  background-blend-mode: screen, normal; /* soft highlight on top of color wash */
}

.card {
  /* subtle card wash using hard stop for a gentle top glow */
  background-image:
    linear-gradient(180deg,
      hsl(0 0% 100% / 1) 0%,
      hsl(0 0% 100% / 1) 22%,
      hsl(0 0% 100% / 0.85) 22%,
      hsl(0 0% 100% / 0.85) 100%
    );
}

How This Works (Code Breakdown)

linear-gradient() accepts a direction followed by one or more color stops. The hero’s main layer uses 135deg, which points from the top left toward the bottom right. You can use “to bottom right” if you prefer named directions. All stops use HSL, which makes hue shifts predictable across the range. The second layer uses to right and fades white from 22% alpha to 0. That soft band simulates a light source without an image.

Each color stop has a position. A stop at 45% locks the mid color where you want the hue to change, which keeps the gradient break consistent across screen sizes. The card example shows a hard stop: two consecutive stops at 22% with different alpha values create a crisp edge in the gradient. Hard stops are perfect for stripes and separators without extra elements.

If your hero needs a curved divider below it, a gradient alone will not draw that shape. For a neat, reusable option, see the CSS wave divider pattern in how to make a CSS wave page divider.

Step 4: Building the Gradient Border Box

You can fake a border with two backgrounds and transparent border color. The trick is to paint the content box on top, the gradient border under it, and let the transparent physical border reveal the lower layer. This gives you high-DPI gradient borders with full control over width and radius.

/* CSS */
.border-gradient-box {
  border: 4px solid transparent; /* lets the lower layer show through */
  border-radius: 0.75rem;

  /* Top layer paints the fill; bottom layer paints the border. */
  background:
    linear-gradient(var(--paper), var(--paper)) padding-box,
    linear-gradient(90deg, var(--brand-2), var(--brand-4)) border-box;

  box-shadow: 0 4px 18px var(--shadow);
}

/* Optional: a subtle inner wash for depth */
.border-gradient-box::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(180deg, hsl(0 0% 100% / 0.6), hsl(0 0% 100% / 0));
}

.border-gradient-box {
  position: relative; /* anchors ::before */
}

How This Works (Code Breakdown)

background uses two layers. The first is linear-gradient(var(–paper), var(–paper)) and is constrained by padding-box, so it only fills the inner area. The second is linear-gradient(90deg, var(–brand-2), var(–brand-4)) and is drawn to the border-box, which includes the transparent border. The transparent border reveals that lower layer, resulting in a clean gradient frame. This approach works with any radius and does not blur on zoom.

Angles create different border vibes. 90deg moves the gradient left to right; switch to 45deg for a diagonal frame. Use hard stops to build multicolor ticks along the border if you want a segmented frame, for example: color A 0 25%, color B 25% 50%, repeat. If you need a notched ribbon tail or a triangular corner cut, grab a real triangle with borders, such as in how to make a triangle right with CSS, then layer your gradient behind it.

Advanced Techniques: Adding Animations & Hover Effects

Gradients animate well when you move background-position across a scaled background-size. You can also generate crisp stripes with repeating-linear-gradient() and slide them for motion. Keep motion respectful of user preferences and surface it only where the interaction calls for it.

/* CSS */
/* 1) Animated gradient button */
.btn {
  background-image: linear-gradient( 90deg,
    var(--brand-1) 0%,
    var(--brand-2) 50%,
    var(--brand-3) 100%
  );
  background-size: 200% 100%; /* allows room to slide */
  transition: transform 0.2s ease;
}

.btn:hover {
  background-position: 100% 0; /* slide the gradient to the left */
  transform: translateY(-1px);
}

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
  .btn { background-size: 100% 100%; transition: none; }
  .btn:hover { background-position: 0 0; transform: none; }
}

/* 2) Diagonal candy stripe divider with motion */
.divider {
  background-image: repeating-linear-gradient( 45deg,
    hsl(220 20% 85%) 0 8px,
    hsl(220 20% 92%) 8px 16px
  );
  background-size: 200% 200%;
  animation: slide-diagonal 6s linear infinite;
}

@keyframes slide-diagonal {
  0% { background-position: 0% 0%; }
  100% { background-position: 100% 100%; }
}

/* 3) Gradient text that gently shifts hue */
.text-gradient {
  /* draw gradient into the element box and clip it to the glyphs */
  background-image: linear-gradient( 90deg,
    var(--brand-1),
    var(--brand-2),
    var(--brand-3),
    var(--brand-4)
  );
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: hue-slide 8s ease-in-out infinite alternate;
}

@keyframes hue-slide {
  0% { background-position: 0% 0%; }
  100% { background-position: 100% 0%; }
}

@media (prefers-reduced-motion: reduce) {
  .divider, .text-gradient { animation: none; }
}

The animated button increases background-size so the gradient has room to move. Hover sets background-position to 100% 0, sliding the colors across the element. The divider uses repeating-linear-gradient() to draw stripes in 8px bands; the @keyframes block shifts background-position diagonally, which creates infinite motion without new paints per stripe. The gradient text relies on background-clip: text and a transparent fill. Some browsers still need the prefixed clip for full support, which is why both declarations are present.

Accessibility & Performance

Gradients are decoration until they affect legibility. Test contrast between foreground text and the underlying gradient at the actual stop positions you use. You can compute the worst case by sampling the brightest and darkest areas. Layer a semi-opaque color stop behind text (as shown in the hero) if you need an extra safety net. Avoid using a gradient as the only way to communicate state. Pair it with text or an icon. Decorative dividers should include aria-hidden=”true” to keep them out of the accessibility tree, which the demo already does on the divider.

Accessibility

Respect motion preferences with prefers-reduced-motion to freeze sliding backgrounds and animated text. Give interactive elements like the button a clear focus style that is independent from the gradient. Screen readers do not need extra labels for visual-only gradient elements. If you create shapes to support a composition, such as circular avatars or badges, a real shape is simpler for layout. If that is your goal, follow how to make a circle with CSS and then layer your gradient inside it.

Performance

Static gradients are cheap. They are drawn once and cached. Multiple layered gradients remain fast across modern engines, even with blend modes. Animation changes things. Moving background-position on large elements increases paint cost. Keep animated areas small, limit the number of layers, and avoid huge background-size values. repeating-linear-gradient() is efficient for stripes because the engine repeats a small pattern. Reserve will-change for cases where you measure jank and need a hint. Long shadows or large blurs combine poorly with animated gradients, so avoid that stack on older devices.

Build Better UI With Fewer Images

You learned the core gradient syntax, how to lock color stops, how to layer multiple gradients, how to draw borders, and how to animate both solid and repeating patterns. That set covers the majority of practical gradient work in production.

Keep a small gradient palette in variables, and treat linear-gradient() as a first-class design primitive. Now you have the tools to paint expressive surfaces, borders, and text without a single image.

Leave a Comment