The background is three times wider than the block and slowly travels back and forth. For people who turned animation off in their system the flow stops — a mandatory part of the recipe.
Stretch the gradient wider than the block — only a third is in view.
02
@keyframes gradient-shift
Move background-position from 0% to 100% and back.
03
animation: … 8s ease-in-out infinite
A slow 6–12 second cycle looks calm and does not distract.
04
@media (prefers-reduced-motion: reduce)
Turn the animation off for those whom motion bothers or harms.
04
Common mistakes
No prefers-reduced-motion
An endless animation with no way to stop it fails WCAG 2.3.3 and can cause discomfort.
A jump at the end of the cycle
If the first and last colours differ, the seam shows a jump. Repeat the first colour at the end or use alternate.
Too fast
A cycle shorter than 4 seconds reads as flashing. For a background, 8 seconds or more is better.
05
Frequently asked
Stretch the background wider than the block with background-size: 300% 100% and describe in @keyframes how background-position moves from 0% to 100% and back. The animation property with infinite and ease-in-out repeats the cycle non-stop. To avoid a jump at the seam, the first colour of the gradient is repeated at the end — the recipe code does it for you. The duration is set with a slider, eight seconds by default.
Animating background-position makes the browser repaint the block on every frame instead of just moving a finished layer. For a button or a card this goes unnoticed, but a full-screen background on a weak laptop can heat the processor and drain the battery. The heavy case is eased by putting the gradient on a pseudo-element wider than the block and moving it with transform — the graphics card runs that without repainting.
It is a media query reporting that the person asked the system to reduce motion on screen: Animation effects in Windows, Reduce motion in macOS and iOS, Remove animations in Android. People with vestibular disorders or migraines do this, and so do those simply tired of flicker. A @media (prefers-reduced-motion: reduce) block with animation: none stops the flow, and the gradient remains a still background.
Take conic-gradient and set its start angle with a variable: from var(--angle). Declare the variable itself with @property and syntax: '<angle>', otherwise the browser cannot animate it smoothly. Then change --angle from 0deg to 360deg in @keyframes. The technique works in Chrome 85+, Safari 16.4+ and Firefox 128+, and in other browsers the gradient simply stands still.
For a background, six to twelve seconds per cycle: the motion is noticeable but does not pull attention from the text. A cycle shorter than four seconds reads as flashing, especially with contrasting colours. Ease-in-out works better than linear: the background slows down at the ends, so the turnaround does not look like a jerk. An accent element such as a button may move faster, but only on hover, not constantly.
The animation-play-state: paused property freezes the flow on the current frame, and running starts it again. It can be switched on hover, with a button or by a class on the root element. A pause is more than a convenience: if motion lasts longer than five seconds next to other content, WCAG criterion 2.2.2 requires a way to stop it. Turning it off through prefers-reduced-motion covers that case only in part.