Home / Snippets / UI Components /
Animated button
Background sweep, shadow lift, and icon shift on hover — all in pure CSS.
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.