Gradient Shift
A three-color animated gradient loops slowly at rest (8 s) and speeds up to 2 s on hover — the speed quadruples with a single CSS animation-duration change. No JavaScript.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Hover has 48 effects, including 5 free. Explore the category →
3 usage examples



How it works
The .fx-0360-root root centers a .hover-box.hover-gradient-shift block of 200 × 200 px. The element has a linear-gradient(135deg, #6366f1 0, #ec4899 50%, #f59e0b 100%) 0 0/200% 200% background — a gradient at 200% width and height, allowing background-position to scroll the colors.
Two @keyframes that are geometrically identical (animating background-position from 0 to 100% and back) but with different names (fx0360-gradientIdle and fx0360-gradientFast) allow switching between two durations: 8 s at rest, 2 s on hover. Changing animation-name on hover restarts the animation from the beginning — having two distinct names guarantees a true restart rather than a simple speed-up of the current animation.
The fx0360- prefix prevents name collisions. Measured in Chromium 151: slow animation at rest, visible speedup on hover, two independent copies.
Accessibility
- Reduced motion: under
prefers-reduced-motion: reduce, all animations stop and the scale is cancelled; on hover the element shows a fixedfilter: brightness(1.15). The idle animation also stops. - Keyboard: the element is a natively focusable
<button>. The:focus-visiblerule triggers the same gradient speed-up; an indigo outline marks the focus state. - Idle animation loop: the idle animation (8 s) runs without interaction. Under
prefers-reduced-motion: reduce, it stops. On a content-heavy animated page, consider starting the idle animation only when the element is visible (IntersectionObserver). - Touch: the first touch triggers the speedup; it stays active until the next touch.
Browser compatibility
Pure CSS (@keyframes, background-position, animation, background-size, :focus-visible, prefers-reduced-motion), no JavaScript. Measured in Chromium 151: animation visible, speedup on hover, two independent copies. Works in all modern browsers.
Without animation, the background stays static at rest and on hover. Without background-size: 200% 200%, the gradient cannot scroll.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0360-root">
<button class="hover-box hover-gradient-shift" type="button">
<span>…</span>
</button>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Base gradient (CSS — background of .hover-gradient-shift) |
Indigo → pink → amber at 200% × 200% | Change the three colors to your palette. Keep 0 0/200% 200% so that the position animation scrolls the gradient. |
Idle duration (CSS — animation: 8s ease infinite fx0360-gradientIdle) |
8 s per cycle | 12s for a very slow, discreet idle; 4s for a more visible idle animation. |
Hover duration (CSS — animation: 2s ease infinite fx0360-gradientFast) |
2 s per cycle (4× faster) | 1s for extreme acceleration; 4s for a gentler speedup. The idle/fast ratio defines the intensity of the signal. |
Scale on hover (CSS — transform: scale(1.05)) |
5% enlargement | Switch to scale(1) to disable the enlargement if the background animation alone is sufficient. |
@keyframes names (CSS — fx0360-gradientIdle / fx0360-gradientFast) |
Prefixed fx0360- | Only rename if you copy the code into an environment where the prefix conflicts. The two names must stay different to guarantee the restart. |
FAQ
animation-name on hover forces the browser to restart the animation from position 0. With a single @keyframes, only changing animation-duration would speed it up without restarting: the current position is kept, which can cause a visual jump.animation property from the .hover-gradient-shift selector and keep it only on :hover and :focus-visible. The background will be static at rest and animate only on hover.const remaining = endTime - Date.now(); el.style.animationDuration = (remaining/1000).toFixed(1)+'s'. The decreasing duration accelerates the animation as the deadline approaches.