buttons
Shiny Button
A button with an animated light sweep across its surface.
"use client";
import * as React from "react";
import { motion, type HTMLMotionProps } from "motion/react";
import { cn } from "@/lib/utils";
export interface ShinyButtonProps extends Omit<HTMLMotionProps<"button">, "ref"> {
children: React.ReactNode;
}
export function ShinyButton({ children, className, ...props }: ShinyButtonProps) {
return (
<motion.button
whileTap={{ scale: 0.97 }}
className={cn(
"relative isolate overflow-hidden rounded-lg border border-white/10 bg-neutral-900 px-6 py-2.5 text-sm font-medium text-neutral-50 shadow-sm dark:bg-neutral-100 dark:text-neutral-900",
className
)}
{...props}
>
<span className="relative z-10">{children}</span>
<motion.span
aria-hidden
className="pointer-events-none absolute inset-0 z-0"
style={{
background:
"linear-gradient(120deg, transparent 30%, rgba(255,255,255,0.45) 50%, transparent 70%)",
backgroundSize: "200% 100%",
}}
initial={{ backgroundPositionX: "150%" }}
animate={{ backgroundPositionX: "-50%" }}
transition={{
repeat: Infinity,
repeatType: "loop",
duration: 2.2,
ease: "linear",
repeatDelay: 0.8,
}}
/>
</motion.button>
);
}
A UX spec for this pattern — written for agents implementing or reusing it, not the code.
# Shiny Button
## Summary
A primary call-to-action button with a looping light-sweep animation across its surface. The motion exists purely to draw the eye to the single most important action on the screen.
## When to use
- The one primary CTA on a screen (hero "Get Started", "Upgrade", "Try it now").
- Actions that begin the product's core flow.
## When not to use
- Secondary or tertiary actions — use a plain or outline button instead.
- More than one per view. The animation only works as a signal if it's rare; two shiny buttons on screen cancel each other out.
- Destructive actions (delete, cancel subscription). The celebratory motion sends the wrong emotional signal.
- A button that stays disabled or pending for a long time — stop the animation rather than looping over a dead action.
## Anatomy
- Label (text or short phrase).
- Looping sheen overlay (decorative, non-interactive).
- Press feedback (slight scale-down on tap/click).
## Behavior
- The sheen loops continuously and indefinitely while the button is enabled — it is not tied to a loading state.
- Press/tap gives immediate tactile feedback (subtle scale), independent of the sheen loop.
- On disabled state: stop the sheen animation and reduce opacity. A looping animation on a dead button reads as broken, not exciting.
## Content guidelines
- Short imperative verb phrase: "Get Started", "Try it", "Upgrade". No trailing punctuation.
## Accessibility
- Must render as a real `<button>` or link element, not a styled `<div>`.
- The sheen must never reduce the label's contrast below WCAG AA against the button background.
- The animation is decorative only — pause or remove it when the user has `prefers-reduced-motion` set.
## Related patterns
- None. This is a standalone action affordance, not part of an agent-status sequence.