Colour · gradient recipeCSS gradient button — with a hover effect
The button’s background is twice as wide as the button and slides smoothly on hover. A gradient cannot be animated between values, but the background position can.
Repeat the first colour at the end so the shift is seamless.
02
background-size: 200% 100%
Make the background twice as wide as the button — only half is visible.
03
:hover { background-position: 100% 50% }
Shift the background on hover; transition makes it smooth.
04
:focus-visible { outline: … }
Keep a visible focus for keyboard users — the gradient does not replace it.
04
Common mistakes
transition on background does nothing
Browsers cannot morph one gradient into another. Animate background-position or the opacity of a pseudo-element.
Label unreadable at the light end
White text passes on the dark half and fails on the light one. Check contrast along the whole length — the tool does it on the right.
outline removed
outline: none without a replacement leaves keyboard users without focus — a WCAG 2.4.7 failure.
05
Frequently asked
The transition property cannot morph one gradient into another — the background swaps instantly. Two techniques work. The first makes the background twice as wide as the button and moves background-position, as in the recipe code. The second puts the second gradient on a ::before pseudo-element and fades its opacity from zero to one. The first is simpler; the second lets you move to completely different colours.
One that keeps 4.5:1 contrast with every point of the background, not just the middle. A white label works if both ends of the gradient are dark enough; at a light end it fails. The tool on this page checks white text along the whole length and reports the worst point. If it is below the threshold, darken the light colour or use a dark label and check it in the readability section of the main gradient page.
In Tailwind version three the background comes from bg-gradient-to-r from-… via-… to-… classes, with the first colour repeated at the end. The background width comes from bg-[length:200%_100%], the starting position from bg-left, the hover shift from hover:bg-right, and smoothness from transition-all duration-300. Keep a visible focus with focus-visible:outline and outline-offset-2. In version four the direction is written as bg-linear-to-r; the rest stays.
Yes, and it costs one line: before the gradient, set background to a solid colour, usually the first colour of the transition. A browser that cannot parse the gradient line, for example because of in oklch, takes the solid colour, and the button stays visible and readable. Without a fallback such a button turns into a transparent rectangle with a white label on a white page, disappearing along with its label.
Two or three neighbouring hues are enough: the small surface of a button cannot hold a long transition, and five colours on it become a gaudy stripe. For the shift effect the first colour repeats at the end, so the code actually holds one more. Opposite colours such as blue and orange are better blended in oklch — otherwise a muddy patch appears in the middle of the button.
For :active a slight downward shift with transform: translateY(1px) and a smaller shadow is enough — there is no need to touch the gradient. You can darken the background by layering a semi-transparent coat on top: background: linear-gradient(rgba(0, 0, 0, .12), rgba(0, 0, 0, .12)) followed by the original gradient. Make a disabled button grey rather than transparent: a faded gradient on a busy page still looks clickable.