components

Creating a Button Shine Effect

Published: September 9, 2026

⏱️ 2 min read | 📝 380 words

Micro-interactions matter

A button that only changes color on hover feels flat. The best UIs — game menus especially — reward interaction with small, satisfying details. One of the cheapest wins is a shine sweep: a highlight that glides across the button when you hover over it.

The best part? You can do it with zero JavaScript, using nothing but a pseudo-element and a CSS transition.

The effect

Pure CSS, no JS

The shine is a ::after pseudo-element layered on top of the button. On hover, a CSS transform slides it from left to right — a single property change that's GPU-friendly and buttery smooth.

The component

---
const { label = "Hover Me" } = Astro.props;
---

<button class="game-btn">{label}</button>

<style>
  .game-btn {
    position: relative;
    padding: 12px 28px;
    font-size: 1rem;
    font-weight: 600;
    color: #fff;
    background: linear-gradient(135deg, #1e293b, #0f172a);
    border: 1px solid #334155;
    border-radius: 8px;
    cursor: pointer;
    overflow: hidden;
    transition: transform 0.15s ease, box-shadow 0.15s ease;
  }

  .game-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
  }

  .game-btn::after {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: linear-gradient(120deg, transparent, rgba(255, 255, 255, 0.3), transparent);
    transform: translateX(-100%);
    transition: transform 0.4s ease;
    pointer-events: none;
  }

  .game-btn:hover::after {
    transform: translateX(100%);
  }
</style>

How it works

  1. overflow: hidden on the button clips the shine inside the rounded corners — no stray highlight bleeding out.
  2. The ::after element starts off-screen at translateX(-100%).
  3. On hover it transitions to translateX(100%), sweeping across the whole surface.
  4. pointer-events: none ensures the overlay never blocks clicks on the button.

Tuning it

Want a faster or subtler sweep? Just adjust the numbers:

.game-btn::after {
  transition: transform 0.25s ease-out; /* quicker shimmer */
  background: linear-gradient(120deg, transparent, rgba(255, 255, 255, 0.15), transparent); /* subtler shine */
}

Changing the angle in linear-gradient changes the direction and shape of the highlight — try 90deg for a straight vertical sweep, or -120deg for a reverse angle.

Respect reduced motion

If you're shipping this, wrap the transition in a @media (prefers-reduced-motion: reduce) query and drop the sweep for users who prefer less animation.

One more UI element

The button lives in the UI Elements Playground, alongside the reusable progress bar. If you want a matching progress component, read the ProgressBar post too.