Buttons
Effect Button
One button, seven visual effects — shimmer, shine, spotlight, glow, gradient, glass and ripple as cva variants.
Preview in your theme
Loading preview…
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/effect-button.jsonPrompt
The prompt behind this component — paste it into your AI assistant to recreate or adapt it.
Build a React + TypeScript + Tailwind "EffectButton" component using
class-variance-authority (cva) for variants.
Contract
- Export a forwardRef button extending React.ButtonHTMLAttributes plus
VariantProps<typeof effectButtonVariants>.
- One prop drives everything: effect = "shimmer" | "shine" | "spotlight" |
"glow" | "gradient" | "glass" | "ripple" (default "shimmer").
- Preserve all consumer handlers (onClick / onMouseMove / onMouseEnter /
onMouseLeave): internal effect logic runs first, then the consumer's handler.
Behavior
- shimmer: a 1/3-width skewed light bar sweeps across the surface on an
infinite 2.75s keyframe loop.
- shine: the same light bar, but idle off-canvas; on hover it sweeps once
(transition duration 700ms on hover-in, 0ms snap-back on hover-out).
- spotlight: track the cursor via onMouseMove into local {x, y} state; render a
radial-gradient that follows the cursor, fading in/out on enter/leave (300ms).
- glow: a pulsing box-shadow keyframe (2.4s loop) around the button.
- gradient: background-size 200% with background-position left→right on hover.
- glass: translucent background + backdrop-blur + subtle border; no animation.
- ripple: on click, append {id, x, y, size} to state and render an expanding
circle from the click point (600ms, scale 0→3, fade out); remove each ripple
in onAnimationEnd — no timers to leak. Skip spawning entirely when
prefers-reduced-motion matches.
- Ship the three @keyframes inside the component with a React 19 hoisted
<style href="..." precedence="medium"> tag so no Tailwind config edits are
needed and duplicate buttons dedupe to one style tag.
Rendering & styling
- Semantic tokens only: bg-primary / text-primary-foreground for solid skins,
primary-foreground at low opacity for light-bar and ripple overlays,
color-mix(in oklab, var(--primary) N%, transparent) for glow shadows,
bg-background + border-border for glass. No hardcoded colors — every effect
adapts to the host theme and dark mode.
- All overlays: absolute, pointer-events-none, aria-hidden, rendered under a
z-10 label span; decorative animation layers are motion-reduce:hidden.
- Base classes via cva: rounded-lg px-6 py-2.5, focus-visible ring,
disabled:opacity-50, active:scale-[0.98]; merge consumer className via cn().
Customization levers
- Add a skin: one cva variant entry + (optionally) one overlay span — the
contract and every other effect stay untouched.
- Sweep speed / glow intensity: the keyframe durations and the color-mix
percentages are the only knobs; keep them paired (slower sweep reads better
with lower opacity).
- Size: override px/py via className, or add a cva `size` axis if you need a
documented scale (sm / md / lg).
- Ripple color: swap bg-primary-foreground/30 for bg-primary/30 when placing
the ripple variant on secondary surfaces.
- Spotlight radius: the 120px in the radial-gradient; 80–150px is the useful
range (match your button size).Concepts
- One contract, many skins — the
effectprop selects a cva variant plus (at most) one overlay span; swapping visual styles is a prop change, so design iteration never touches call sites. - Overlay layering — every effect renders into an absolutely-positioned,
aria-hidden,pointer-events-nonelayer beneath az-10label, so effects can never intercept clicks or confuse screen readers. - Hoisted keyframes — React 19 dedupes
<style href precedence>tags into the document head, letting a registry component ship its own@keyframeswith zero Tailwind config edits. - Mouse-tracked spotlight — cursor position feeds a
radial-gradientthrough local state;color-mixon--primary-foregroundkeeps the light readable on the primary surface in both color schemes. - Self-cleaning ripples — each ripple removes itself in
onAnimationEndinstead ofsetTimeout, so unmounting mid-animation leaks nothing. - Reduced-motion honesty — looping and pointer overlays are
motion-reduce:hidden, and ripples are never spawned underprefers-reduced-motion; the button itself keeps full function.