Home / Snippets / UI Components /

Animated button

Background sweep, shadow lift, and icon shift on hover — all in pure CSS.

Widely Supported
uianimation

Quick implementation

/* HTML: <button class="ani-btn"><span>Get started</span><span class="ani-btn-icon">→</span></button> */

.ani-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1.75rem;
  border: none;
  border-radius: 0.5rem;
  background: oklch(0.52 0.22 265);
  color: oklch(1 0 0);
  font-weight: 600;
  cursor: pointer;
  overflow: hidden;
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}

.ani-btn::before {
  content: '';
  position: absolute;
  inset: 0;
  background: oklch(1 0 0 / 0.15);
  transform: translateX(-100%);
  transition: transform 0.35s ease;
}

.ani-btn:hover::before { transform: translateX(0); }

.ani-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 20px oklch(0.52 0.22 265 / 0.35);
}

.ani-btn .ani-btn-icon {
  transition: transform 0.25s ease;
}

.ani-btn:hover .ani-btn-icon {
  transform: translateX(3px);
}

.ani-btn span, .ani-btn .ani-btn-icon {
  position: relative;
  z-index: 1;
}

@media (prefers-reduced-motion: reduce) {
  .ani-btn, .ani-btn::before, .ani-btn .ani-btn-icon {
    transition: none;
  }
}

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer building interactive UI components.

Goal: A button with a layered hover animation — background sweep via ::before, vertical lift via translateY, box-shadow growth, and icon nudge — no JavaScript.

Technical constraints:
- Use ::before pseudo-element with translateX(-100%) → 0 for the sweep effect.
- The sweep overlay must sit behind button text — use z-index layering.
- Lift uses translateY(-2px) and box-shadow with oklch() alpha for the glow.
- Icon element shifts 3px right on hover via transform.
- All transitions use ease timing, staggered durations (0.25s lift, 0.35s sweep).
- Use oklch() for all color values, not hex or rgba().
- Wrap all transitions in @media (prefers-reduced-motion: reduce).

Framework variant (pick one):
A) Vanilla HTML + CSS only.
B) React component — accept children, variant (filled | outline), icon (ReactNode), onClick, and disabled props.

Edge cases to handle:
- Active/pressed state: snap back to translateY(0) for tactile feedback.
- Disabled: suppress all animations and lower opacity.
- Outline variant: sweep fills with accent color and text switches to white.
- Focus-visible ring must remain visible over the ::before overlay.

Return HTML + CSS.

Why this matters in 2026

Static buttons feel dead. A well-choreographed hover animation — sweep, lift, glow — communicates interactivity and quality without overwhelming the user. Modern CSS handles all three effects with ::before, transform, and box-shadow transitions, keeping the animation on the compositor thread. No animation library or JavaScript event listeners needed.

The logic

The ::before pseudo-element creates a semi-transparent overlay that starts fully off-screen (translateX(-100%)) and slides in on hover. The button text and icon sit above it via z-index: 1. Simultaneously, the button itself lifts with translateY(-2px) and gains a colored box-shadow. The icon gets its own translateX(3px) nudge with a slightly different timing, creating a staggered, multi-stage feel. On :active, the lift snaps back to zero for tactile press feedback.

Accessibility & performance

The @media (prefers-reduced-motion: reduce) query disables all transitions for motion-sensitive users — the visual states still change, just instantly. The :focus-visible outline uses outline-offset to stay visible above the ::before overlay. All animated properties — transform, box-shadow, opacity — are compositor-friendly, meaning the browser can animate them without triggering layout or paint. This keeps the animation at 60fps even on low-powered devices.