---
name: animated-gradient-background
description: Use when you want "Modern, friendly, polished" - Moves gradient colors slowly across the page.
---

# Animated Gradient Background

> **Category:** Background  -  **Personality:** Modern, friendly, polished
>
> **Best use:** Hero sections, startup landing pages
>
> **Ratings:** Professional 4/5 - Casual 5/5 - Exotic 3/5 - Visual impact 4/5 - Difficulty 2/5 - Performance cost 3/5

## What it is

A full-stage backdrop made of a single multi-colour gradient that slowly slides and cycles its hue, so the surface feels alive without ever demanding attention. It's the everyday "animated gradient" you see behind startup and landing-page heroes — friendly, modern and polished, with hero copy layered on top. The whole thing is one CSS-animated layer: no canvas, no WebGL, no JavaScript required for the motion itself.

Keep it distinct from its cousins: a **mesh gradient** is built from overlapping radial blobs, and a **fluid gradient** morphs organically. This effect is the simple, directional slide of one oversized linear gradient.

## Dependencies / CDN

**None — pure CSS** for the effect. The demo loads two display/body fonts (optional) and uses a few lines of vanilla JS only to power the live palette switcher:

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Familjen+Grotesk:wght@400;500;600;700&family=Unbounded:wght@600;700;800&display=swap" rel="stylesheet">
```

## HTML

A decorative gradient layer behind a normal content layer:

```html
<div class="stage" data-theme="spectra">
  <div class="gradient" aria-hidden="true"></div>
  <div class="content">
    <h1>Brands that <em>move</em> with you.</h1>
    <p>The living brand studio where your colour, type and motion stay in sync.</p>
    <button>Start building free</button>
  </div>
</div>
```

## CSS

```css
/* THE EFFECT — an oversized gradient whose position slides, plus a slow hue cycle */
.gradient{
  position:absolute; inset:0; z-index:0;
  background:linear-gradient(120deg,#7c3aed,#d946ef,#fb7185,#fb923c,#22d3ee,#7c3aed);
  background-size:300% 300%;                 /* MUST be > 100% or there is nothing to slide */
  animation:bg-shift 19s ease-in-out infinite, bg-hue 34s linear infinite;
  will-change:background-position, filter;
}
@keyframes bg-shift{
  0%  {background-position:0% 50%}
  50% {background-position:100% 50%}
  100%{background-position:0% 50%}
}
@keyframes bg-hue{
  0%  {filter:saturate(1.05) hue-rotate(0deg)}
  100%{filter:saturate(1.05) hue-rotate(360deg)}
}

/* the stage clips the gradient; content sits above it */
.stage{position:relative; overflow:hidden; border-radius:28px; min-height:620px; isolation:isolate}
.content{position:relative; z-index:2; color:#fff}

/* accessibility: freeze on a calm, centred static gradient */
@media (prefers-reduced-motion: reduce){
  .gradient{animation:none; background-position:50% 50%}
}

/* OPTIONAL — registered custom props so a palette swap CROSS-FADES instead of snapping */
@property --c1{syntax:'<color>'; inherits:true; initial-value:#7c3aed}
/* …repeat @property for --c2 … --c5 … */
.stage{--c1:#7c3aed;--c2:#d946ef;--c3:#fb7185;--c4:#fb923c;--c5:#22d3ee;
  transition:--c1 .6s,--c2 .6s,--c3 .6s,--c4 .6s,--c5 .6s}
.stage[data-theme="sunset"]{--c1:#581c87;--c2:#be185d;--c3:#e11d48;--c4:#f97316;--c5:#fbbf24}
.stage[data-theme="mint"]  {--c1:#0d9488;--c2:#06b6d4;--c3:#3b82f6;--c4:#6366f1;--c5:#22d3ee}
.gradient{background:linear-gradient(120deg,var(--c1),var(--c2),var(--c3),var(--c4),var(--c5),var(--c1))}
```

## JavaScript

Not required for the effect. The demo's only script swaps the palette by changing `data-theme` (the registered `@property` colours then cross-fade):

```js
const stage = document.querySelector('.stage');
document.querySelectorAll('.chip').forEach(chip => {
  chip.addEventListener('click', () => {
    stage.dataset.theme = chip.dataset.pal;
    document.querySelectorAll('.chip').forEach(c => c.classList.remove('is-active'));
    chip.classList.add('is-active');
  });
});
```

## How it works

- **Oversized canvas to slide.** `background-size:300% 300%` makes the gradient three times the element, so most of it lives off-screen. Animating `background-position` from `0% 50%` → `100% 50%` → back glides that oversized gradient through the visible window, dragging the colour stops across the panel.
- **A second hue cycle keeps it fresh.** `filter:hue-rotate(0 → 360deg)` slowly rotates the entire palette so the loop never lands on the exact same colours twice. Using two *different* durations (19s vs 34s) means the combined motion has no obvious repeat point.
- **Easing sets the mood.** `ease-in-out` on the position glides and settles at each end; `linear` on the hue keeps the colour drift perfectly steady.
- **Layering protects the text.** The gradient is a sibling layer behind the content (`z-index`), so the headline stays sharp and never inherits the `hue-rotate` filter.

## Customization

- **Speed:** raise or lower the two durations. Keep them un-synced (e.g. 19s & 34s) so the motion doesn't visibly loop.
- **Direction:** change the gradient `angle` (120deg) or animate `background-position` on the Y axis (`50% 0%` → `50% 100%`) for a vertical drift.
- **Palette:** swap the stops — 4–6 saturated colours read best; repeat the first colour at the end for a seamless loop. The three demo themes (`spectra` / `sunset` / `mint`) show the range.
- **Calmer version:** drop the `bg-hue` animation for a fixed-palette slide, or widen `background-size` to `400%+` for longer, lazier travel.
- **Live theming:** the `@property`-registered custom props let you cross-fade between palettes at runtime instead of snapping.

## Accessibility & performance

- Cheap and GPU-friendly: one painted layer, no JS loop. `hue-rotate` does repaint the layer, so keep it on a single full-bleed element rather than many.
- **Always ship the `prefers-reduced-motion` fallback** (above) — a constantly shifting full-screen gradient can be uncomfortable for motion-sensitive users.
- Hero text must stay legible *at every frame*, not just the first: use deep, saturated stops, white text, and a subtle dark scrim (radial + linear) so contrast holds as colours move.
- Mark the gradient layer `aria-hidden="true"`; it's purely decorative.

## Gotchas

- **`background-size` must exceed 100%.** At the default size the gradient fills the box exactly and there is nothing to slide — the animation looks frozen.
- **You can't `transition` a gradient between two colour sets directly.** To animate colour swaps, drive the stops with `@property`-registered custom properties so the browser knows they're colours and interpolates them; otherwise the change snaps.
- **Keep `hue-rotate` off any ancestor of your text.** A filter on a parent tints the content too — apply it only to the dedicated background layer.
- **Banding.** A large multi-stop gradient can band on 8-bit displays; a faint noise/grain overlay (as in the demo, ~7% opacity) hides it.
- **`@property` support.** In older browsers the palette swap simply snaps instead of cross-fading — a graceful fallback. The core slide + hue animation works everywhere.
