Skip to content
WANCODE
BM

CSS Gradient Button

A gradient button with a smooth hover shift, a visible keyboard focus ring and reduced-motion support — pure CSS.

Live preview Open in Playground
button.css
.btn-gradient {
  --btn-from: #6d28d9; /* violet */
  --btn-to: #be185d;   /* deep pink */

  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1.25rem;
  border: 0;
  border-radius: 0.75rem;
  font: inherit;
  font-weight: 600;
  color: #fff;
  cursor: pointer;

  /* Twice as wide as the button, so we can slide it */
  background-image: linear-gradient(120deg, var(--btn-from), var(--btn-to), var(--btn-from));
  background-size: 200% 100%;
  background-position: 0% 50%;
  transition: background-position 0.5s ease, transform 0.15s ease;
}

.btn-gradient:hover {
  background-position: 100% 50%;
}

.btn-gradient:active {
  transform: translateY(1px);
}

.btn-gradient:focus-visible {
  outline: 3px solid #f59e0b;
  outline-offset: 3px;
}

.btn-gradient:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

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

Browsers can't animate between two background-image gradients — the change just snaps. The trick is to make the gradient wider than the button (background-size: 200%) and animate its position instead, which is animatable.

Why each part is there

  • The gradient goes from → to → from, so sliding it from 0% to 100% moves smoothly through the colours and ends on a matching colour — no hard edge.
  • font: inherit stops the button from using the browser's default (often smaller) font.
  • :focus-visible shows a strong outline for keyboard users without showing it on every mouse click. Never remove focus styles without replacing them.
  • Both gradient colours keep white text above a 4.5:1 contrast ratio, the WCAG AA minimum for normal text. Check again if you change the colours.
  • prefers-reduced-motion: reduce turns the transition off for people who asked their operating system for less motion. The colour still changes — it just doesn't slide.

Use a real <button> for actions and an <a href> for navigation. The class works on both.

Usage

HTML
<button class="btn-gradient" type="button">Get started</button>

<!-- Different colours for one button -->
<a class="btn-gradient" href="/pricing" style="--btn-from: #0369a1; --btn-to: #4338ca;">
  See pricing
</a>