Three dark pill buttons in small, medium and large, each crossed by a soft diagonal band of warm light. The sweeps are staggered so one beam appears to travel through all three in turn, and a thin highlight on the top edge brightens as it passes. The effect is plain CSS keyframes on a transform, so it needs no animation package. On phones the three specimen cells stack into one column.
import { useEffect, useRef, useState } from "react";
// One beam of light crosses all three buttons in turn: each button starts its sweep a little later.
const SWEEP_SECONDS = 2.4;
const STAGGER_SECONDS = 0.24;
const sizes = [
{ name: "Small", spec: "h-10 / text-sm", label: "Notify me", box: "h-10 gap-2 px-5 text-sm", icon: "h-4 w-4", cell: "sm:col-span-3" },
{ name: "Medium", spec: "h-12 / text-base", label: "Reserve a copy", box: "h-12 gap-2.5 px-7 text-base", icon: "h-4 w-4", cell: "sm:col-span-4" },
{ name: "Large", spec: "h-16 / text-lg", label: "Pre-order the box set", box: "h-16 gap-3 px-8 text-lg", icon: "h-5 w-5", cell: "sm:col-span-5" },
];
export default function ShimmerButtonWithLightSweepInThreeSizes() {
const rootRef = useRef<HTMLElement>(null);
const [paused, setPaused] = useState(false);
// Stop the sweep while the section is scrolled out of view.
useEffect(() => {
const el = rootRef.current;
if (!el || typeof IntersectionObserver === "undefined") return;
const observer = new IntersectionObserver(([entry]) => setPaused(!entry.isIntersecting));
observer.observe(el);
return () => observer.disconnect();
}, []);
return (
<section
ref={rootRef}
data-paused={paused}
className="shimmer-button-with-light-sweep-in-three-sizes relative overflow-hidden bg-stone-100 px-5 py-16 sm:px-8 sm:py-20"
>
<style>{`
@keyframes shimmer-button-with-light-sweep-in-three-sizes-sweep {
0% { transform: translateX(-115%) skewX(-18deg); }
84%, 100% { transform: translateX(270%) skewX(-18deg); }
}
@keyframes shimmer-button-with-light-sweep-in-three-sizes-rise {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes shimmer-button-with-light-sweep-in-three-sizes-edge {
0%, 100% { opacity: 0.35; }
30% { opacity: 1; }
60% { opacity: 0.35; }
}
.shimmer-button-with-light-sweep-in-three-sizes .sbls-rise {
animation: shimmer-button-with-light-sweep-in-three-sizes-rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.shimmer-button-with-light-sweep-in-three-sizes .sbls-sweep {
background: linear-gradient(90deg, rgba(255, 244, 222, 0) 0%, rgba(255, 236, 200, 0.16) 14%, rgba(255, 236, 200, 0.05) 30%, rgba(255, 244, 222, 0.2) 44%, rgba(255, 252, 244, 0.78) 56%, rgba(255, 244, 222, 0.2) 68%, rgba(255, 244, 222, 0) 100%);
animation: shimmer-button-with-light-sweep-in-three-sizes-sweep ${SWEEP_SECONDS}s cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite both;
will-change: transform;
}
.shimmer-button-with-light-sweep-in-three-sizes .sbls-edge {
background: linear-gradient(90deg, rgba(253, 230, 138, 0), rgba(253, 230, 138, 0.9), rgba(253, 230, 138, 0));
animation: shimmer-button-with-light-sweep-in-three-sizes-edge ${SWEEP_SECONDS}s ease-in-out infinite both;
}
.shimmer-button-with-light-sweep-in-three-sizes[data-paused=true] * {
animation-play-state: paused;
}
@media (prefers-reduced-motion: reduce) {
.shimmer-button-with-light-sweep-in-three-sizes .sbls-rise { animation: none; }
.shimmer-button-with-light-sweep-in-three-sizes .sbls-sweep { animation: none; transform: translateX(120%) skewX(-18deg); }
.shimmer-button-with-light-sweep-in-three-sizes .sbls-edge { animation: none; opacity: 0.6; }
}
`}</style>
{/* Paper texture: two soft warm pools of light */}
<div aria-hidden="true" className="pointer-events-none absolute -left-24 -top-32 h-96 w-96 rounded-full bg-amber-200/50 blur-3xl" />
<div aria-hidden="true" className="pointer-events-none absolute -bottom-40 right-0 h-96 w-96 rounded-full bg-orange-200/40 blur-3xl" />
<div className="relative mx-auto max-w-4xl">
<div className="sbls-rise flex items-center gap-3 text-xs font-semibold uppercase tracking-widest text-stone-600">
<span className="h-px w-8 bg-stone-400" />
Halden Press, autumn list
</div>
<h2 className="sbls-rise mt-5 max-w-2xl font-serif text-4xl leading-tight tracking-tight text-stone-950 sm:text-5xl" style={{ animationDelay: "0.06s" }}>
The Salt Road is printed once. <span className="italic text-amber-700">Then the plates are retired.</span>
</h2>
<p className="sbls-rise mt-5 max-w-xl text-base leading-7 text-stone-700" style={{ animationDelay: "0.12s" }}>
1,200 numbered copies, cloth bound, shipping from our Bristol bindery on 14 November. Pick how far in you want to go.
</p>
<div className="mt-12 grid gap-px overflow-hidden rounded-2xl border border-stone-300 bg-stone-300 sm:grid-cols-12">
{sizes.map((size, i) => (
<div key={size.name} className={`flex flex-col items-center justify-between gap-8 bg-stone-50 px-4 pb-5 pt-10 ${size.cell}`}>
<div className="flex h-16 items-center">
<button
type="button"
style={{ animationDelay: `${0.2 + i * 0.09}s` }}
className={`sbls-rise group relative isolate inline-flex shrink-0 items-center justify-center overflow-hidden whitespace-nowrap rounded-full bg-gradient-to-b from-stone-800 to-stone-950 font-semibold text-stone-50 shadow-[inset_0_1px_0_rgba(255,255,255,0.22),0_14px_28px_-12px_rgba(28,25,23,0.75)] transition-transform duration-300 ease-out hover:-translate-y-0.5 active:translate-y-0 active:scale-95 focus:outline-none focus-visible:ring-2 focus-visible:ring-amber-600 focus-visible:ring-offset-2 focus-visible:ring-offset-stone-50 ${size.box}`}
>
<span aria-hidden="true" className="pointer-events-none absolute inset-0 -z-10 bg-gradient-to-br from-amber-100/20 via-transparent to-transparent" />
<span
aria-hidden="true"
className="sbls-sweep pointer-events-none absolute inset-y-0 left-0 -z-10 w-2/5"
style={{ animationDelay: `${0.2 + i * STAGGER_SECONDS}s` }}
/>
<span
aria-hidden="true"
className="sbls-edge pointer-events-none absolute inset-x-6 top-0 -z-10 h-px"
style={{ animationDelay: `${0.2 + i * STAGGER_SECONDS}s` }}
/>
{size.label}
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" className={`${size.icon} transition-transform duration-300 ease-out group-hover:translate-x-1`}>
<path d="M4 10h11m-4-4.5L15.5 10 11 14.5" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
</div>
<div className="flex w-full items-center justify-between border-t border-dashed border-stone-300 pt-4 text-xs">
<span className="font-semibold uppercase tracking-widest text-stone-900">{size.name}</span>
<span className="font-mono text-stone-600">{size.spec}</span>
</div>
</div>
))}
</div>
</div>
</section>
);
}
Install with one command: npx shadcn@latest add https://we.inc/r/shimmer-button-with-light-sweep-in-three-sizes.json
Each button has `overflow-hidden` and contains a narrow span painted with a transparent to white to transparent gradient. A keyframe animation moves that span from left of the button to right of it with `translateX`, tilted by `skewX`. Because only a transform changes, the browser can run it without recalculating layout.
The keyframes finish the movement at 84% of the cycle and hold the span off the right edge until 100%. That rest keeps the effect calm. Move the 84% stop closer to 100% for a continuous shimmer.
No. The component imports only React, and only for the small observer that pauses the animation when the section is off screen. The effect itself is CSS.
Yes. Set `animation-play-state: paused` on `.sbls-sweep` and switch it to `running` under the button's `:hover` and `:focus-visible` states in the style block.
Yes, but white light will not show on a white button. Use a darker sweep such as `rgba(15, 23, 42, 0.12)`, or tint the beam with your accent colour.
Related: Button with Rotating Gradient Glow Border, Magnetic Button That Follows the Pointer, Button Set: Primary, Secondary, Ghost, Icon and Loading.
More animated buttons or all free UI components. Start for free to have a full site built around it.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.