# CSS animated gradient backgrounds, done properly

[Canonical HTML page](https://gradiently.design/guide/css-animated-gradient)

Gradients cannot be animated directly, but everything around them can. Four techniques, what each costs the browser, and how to keep motion calm and optional.

## The short version

- Browsers cannot interpolate between two gradient values, so a CSS animated gradient moves, fades or transforms the gradient instead of animating it directly.
- The classic technique makes the background larger than the element and animates background-position, which works everywhere but repaints every frame.
- Registering custom properties with @property lets the browser animate individual colours, angles and positions inside a gradient.
- Animating transform or opacity on a separate gradient layer is usually the cheapest option, because browsers can often composite it without repainting.
- Every gradient animation should stop, or become nearly still, when the user has asked for reduced motion.

A **CSS animated gradient** is a gradient background that shifts colour or position over time. There is a catch: if you write two different gradients in a `@keyframes` rule, browsers do not blend between them, they jump. So every working technique animates something next to the gradient instead: its position, the custom properties inside it, or the layer it is painted on. Which one you choose decides how smooth it looks and how much work the browser does each frame.

- Frame 1: dusk: `linear-gradient(120deg, #1e1b4b, #7c3aed 45%, #f472b6)`
- Frame 2: later: `linear-gradient(160deg, #1e1b4b, #6d28d9 35%, #fb7185)`
- Frame 3: ember: `linear-gradient(200deg, #1e1b4b, #4c1d95 30%, #fb923c)`

Three moments of one slow animation. Direction and colour both move, but the palette stays one family.

For the design side, when motion helps a brand and how to pace it, see [animated gradient backgrounds](https://gradiently.design/guide/animated-gradient-background). This guide is the code.

## Technique 1: move an oversized background

Make the gradient two to four times the size of the element with `background-size`, then animate `background-position` so a different part of it shows over time. It works in every browser and needs no JavaScript. Use `alternate` so the motion reverses smoothly instead of jumping back to the start.

```css
.hero {
  background-color: #1e1b4b;
  background-image: linear-gradient(120deg, #1e1b4b, #7c3aed, #f472b6, #fb923c);
  background-size: 300% 300%;
  animation: pan 18s ease-in-out infinite alternate;
}

@keyframes pan {
  from { background-position: 0% 50%; }
  to   { background-position: 100% 50%; }
}
```

The classic animated gradient. Slow durations of 12 to 30 seconds feel ambient rather than busy.

The cost: `background-position` is not something browsers can move on the graphics card alone, so the element is repainted on every frame. On a button that is nothing. On a full-screen hero it is real work that runs for as long as the page is open.

## Technique 2: animate colour stops with @property

Custom properties are normally strings, which cannot be interpolated. Registering one with `@property` gives it a type, such as `<color>`, `<angle>` or `<percentage>`, and typed properties animate smoothly. Use them inside the gradient and you can animate the colours, the angle or the position of a stop directly. `@property` is supported in all current major browsers; check [caniuse](https://caniuse.com/mdn-css_at-rules_property) if you support older ones.

```css
@property --from {
  syntax: "<color>";
  inherits: false;
  initial-value: #7c3aed;
}
@property --to {
  syntax: "<color>";
  inherits: false;
  initial-value: #f472b6;
}
@property --angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 120deg;
}

.hero {
  background: linear-gradient(var(--angle), var(--from), var(--to));
  animation: shift 16s ease-in-out infinite alternate;
}

@keyframes shift {
  to {
    --from: #0891b2;
    --to: #fb923c;
    --angle: 200deg;
  }
}
```

Colours and angle animating inside the gradient. Unregistered properties would jump instead of blend.

- Start: violet to pink: `linear-gradient(120deg, #7c3aed, #f472b6)`
- End: cyan to orange: `linear-gradient(200deg, #0891b2, #fb923c)`

The two ends of the animation above. The browser blends every frame in between.

This technique still repaints every frame, like the first. Its advantage is control: you can move one glow in a [mesh gradient](https://gradiently.design/guide/css-mesh-gradient), rotate a [conic border](https://gradiently.design/guide/css-gradient-border), or shift a brand gradient towards a second palette without any oversized backgrounds.

## Technique 3: transform a gradient layer

Put the gradient on a pseudo-element much larger than the element, and animate its `transform`, for example a slow rotation or drift. Browsers can usually move a transformed layer on the graphics card without repainting it, so this is the cheapest way to animate a large gradient. `will-change: transform` hints that the layer should be promoted in advance.

```css
.hero {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

.hero::before {
  content: "";
  position: absolute;
  inset: -50%;
  z-index: -1;
  background: conic-gradient(from 0deg, #1e1b4b, #7c3aed, #f472b6, #0891b2, #1e1b4b);
  animation: turn 40s linear infinite;
  will-change: transform;
}

@keyframes turn {
  to { transform: rotate(1turn); }
}
```

A huge conic gradient turning slowly behind the content. The negative inset keeps the corners covered as it rotates.

## Technique 4: crossfade two gradients

Stack two gradient layers and animate the `opacity` of the top one. Opacity is also a compositor-friendly property, so the fade is smooth and cheap. It suits slow mood changes, such as a day palette fading into a night palette, and it lets the two gradients be completely different in structure.

```css
.sky {
  position: relative;
  isolation: isolate;
  background: linear-gradient(180deg, #7dd3fc, #fde68a);
}

.sky::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(180deg, #0f172a, #7c3aed);
  animation: dusk 20s ease-in-out infinite alternate;
}

@keyframes dusk {
  from { opacity: 0; }
  to   { opacity: 1; }
}
```

Day fades into night and back. Only opacity changes, so nothing is repainted.

## Choosing a technique

| Technique | Browser work per frame | Best for |
| --- | --- | --- |
| Moving `background-position` | Repaint | Small elements, buttons, text |
| `@property` colours and angles | Repaint | Precise colour changes, rotating borders |
| `transform` on a layer | Usually composited only | Large, full-screen backgrounds |
| `opacity` crossfade | Usually composited only | Slow mood or palette changes |

Repaints are fine on small areas. For large areas, prefer transform and opacity.

Measure rather than guess. In Chrome DevTools, the Rendering panel's paint flashing highlights areas repainted each frame, and a Performance recording shows how much of each frame goes to painting. [CSS gradient performance](https://gradiently.design/guide/css-gradient-performance) walks through it.

## Reduced motion and good manners

Some people get dizzy or distracted by background motion, and operating systems let them ask for less of it. Honour that with a `prefers-reduced-motion` query that stops the animation, leaving a still, finished frame. The [reduced motion](https://gradiently.design/guide/reduced-motion) guide explains what counts as motion worth removing.

```css
@media (prefers-reduced-motion: reduce) {
  .hero,
  .hero::before,
  .sky::after {
    animation: none;
  }
}
```

One rule that stops every technique above. Check that the frame left behind still looks right.

### Avoid

- Cycles faster than about 8 seconds on large areas
- Rainbow hue rotation across the whole page
- Motion behind body text people need to read
- Animations that keep running when the tab is idle for minutes on end

### Do

- Slow, ambient movement within one colour family
- Motion in the hero, stillness in the content
- A calm still frame for reduced motion
- Test on a modest laptop and phone, not just a fast desktop

## FAQ

### Can you animate a CSS gradient?

Not directly: browsers jump between two gradient values in keyframes. Animate `background-position`, registered custom properties used inside the gradient, or the transform and opacity of a gradient layer instead.

### What is the smoothest way to animate a gradient background?

Animating `transform` or `opacity` on a separate gradient layer, because browsers can usually handle those without repainting.

### How do I animate gradient colours in CSS?

Register each colour with `@property` using `syntax: "<color>"`, use the properties in the gradient, and change them in `@keyframes`.

### Do animated gradients slow down a page?

Small ones rarely matter. Large animated backgrounds that repaint every frame can cost battery and smoothness, so prefer transform or opacity and measure with DevTools.

### How do I respect reduced motion for gradient animations?

Wrap an `animation: none` rule in `@media (prefers-reduced-motion: reduce)` so users who ask for less motion see a still frame.
