NEAT

Animated CSS gradient background

There are two ways to get a moving gradient behind a page, and they are not interchangeable. This page gives you working CSS for the first, and explains where it stops being enough.

Open the editor 25 presets

The pure CSS version

The standard trick is to make the gradient much wider than the viewport and animate background-position. Nothing else in CSS will interpolate gradient colour stops, so this is the technique whether or not it is the one you wanted:

.animated-gradient {
  background: linear-gradient(120deg, #FF5772, #4CB4BB, #FFC600, #8B6AE6);
  background-size: 400% 400%;
  animation: gradient-drift 18s ease infinite;
}

@keyframes gradient-drift {
  0%   { background-position:   0% 50%; }
  50%  { background-position: 100% 50%; }
  100% { background-position:   0% 50%; }
}

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

What that gets you, and what it does not

It is about ten lines, needs no JavaScript, works everywhere, and costs essentially nothing. For a lot of sites that is the correct answer and you can stop reading here.

The limitation is structural rather than a matter of effort. You are sliding a fixed image behind a window, so the motion is always linear and always loops visibly. There is no depth, because there is no surface being lit. Colours cannot pool or fold into each other, because linear-gradient() only interpolates along one axis. And on a large viewport, animating background-position on a huge background image is one of the more expensive things you can ask a compositor to do repeatedly.

A slightly better-behaved variant animates a transform on an oversized pseudo-element instead, which keeps the work on the GPU:

.gradient-host { position: relative; overflow: hidden; isolation: isolate; }

.gradient-host::before {
  content: "";
  position: absolute;
  inset: -50%;
  z-index: -1;
  background: conic-gradient(from 0deg, #FF5772, #4CB4BB, #FFC600, #8B6AE6, #FF5772);
  filter: blur(80px);
  animation: gradient-spin 24s linear infinite;
  will-change: transform;
}

@keyframes gradient-spin { to { transform: rotate(1turn); } }

When to use WebGL instead

If you need the colours to actually move through each other rather than past the window — folding, pooling, catching light — CSS cannot express that. It has no concept of a surface with geometry.

Neat renders a displaced 3D plane on the GPU and blends up to six colours across it in a fragment shader. That is where the depth comes from. The cost is roughly 17 KB gzipped and a <canvas> element:

import { NeatGradient } from "@firecms/neat";

const gradient = new NeatGradient({
  ref: document.getElementById("gradient"),
  colors: [
    { color: "#FF5772", enabled: true },
    { color: "#4CB4BB", enabled: true },
    { color: "#FFC600", enabled: true },
    { color: "#8B6AE6", enabled: true },
  ],
  speed: 2.5,
  waveAmplitude: 5,
  colorSaturation: 7,
});

// Respect the user's motion preference
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
  gradient.speed = 0;
}

A note on reduced motion

Whichever route you take, honour prefers-reduced-motion. A full-viewport animated background is exactly the category of motion that setting exists for, and both examples above show how to disable it. A still gradient is not a degraded experience — several of the presets here are close to motionless by design.

Questions

How do I animate a CSS gradient?

Make the gradient larger than its container with background-size, then animate background-position in a keyframe loop. CSS cannot interpolate gradient colour stops directly, so moving the gradient behind a fixed window is the available technique.

Is an animated CSS gradient bad for performance?

Animating background-position on a full-viewport element forces repeated compositing work and can cost noticeably on low-end devices. Animating a transform on an oversized blurred pseudo-element is cheaper because it stays on the GPU.

Can CSS produce a mesh gradient?

Approximately, by layering several radial-gradient() backgrounds at different positions. It works for a static flat mesh but cannot animate smoothly or produce depth shading.

Does Neat output CSS?

No. Neat renders to a canvas with WebGL, so the export is a JavaScript config object, a PNG, or a video file. If you specifically need a CSS string, use the copy-paste code on this page.

Keep reading