Shimmer Button

A dark capsule with a travelling light sweep and a rotating border glow for your one important call to action.

  • Buttons
  • AI / SaaS
  • React only · 0 deps
  • 1 file
Full screen

Browser support · Rotating ring uses @property (Chrome, Edge, Safari 16.4+, Firefox 128+); older browsers show a static ring.

Install. One command, or copy the file.

$npx shadcn@latest add https://freedesignmd.com/r/shimmer-button.json
  • Byte-identical to the preview
  • No Tailwind or config needed
  • SSR-safe · keyboard accessible
  • Respects reduced motion & transparency
components/buttons/shimmer-button.tsx
1/**
2 * ShimmerButton — dark capsule with a travelling light sweep and a rotating
3 * border glow. Built for single, high-intent calls to action.
4 *
5 * Props: size ("sm" | "md" | "lg"), iconRight, speed (seconds per sweep,
6 * default 3.2), theme ("light" | "dark" | inherits [data-fd-theme]), plus every
7 * native <button> prop. CSS vars: --fd-shim-bg, --fd-shim-glow, --fd-shim-radius.
8 *
9 * Browser support: sweep works everywhere; the rotating ring uses @property
10 * (Chrome, Edge, Safari 16.4+, Firefox 128+) and degrades to a static ring.
11 * Accessibility: native <button>, visible focus ring, effects are
12 * aria-hidden. Reduced motion: sweep and ring animation are removed.
13 */
14import { forwardRef, useState } from "react";
15import type { ButtonHTMLAttributes, ReactNode } from "react";
16
17export interface ShimmerButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
18 size?: "sm" | "md" | "lg";
19 iconRight?: ReactNode;
20 speed?: number;
21 theme?: "light" | "dark";
22}
23
24export const ShimmerButton = forwardRef<HTMLButtonElement, ShimmerButtonProps>(function ShimmerButton(
25 { size = "md", iconRight, speed = 3.2, theme, className, style, children, type = "button", ...rest },
26 ref,
27) {
28 return (
29 <>
30 <style>{css}</style>
31 <button
32 ref={ref}
33 type={type}
34 className={`fd-shim${className ? " " + className : ""}`}
35 data-size={size}
36 data-fd-theme={theme}
37 style={{ ["--fd-shim-speed" as string]: `${speed}s`, ...style }}
38 {...rest}
39 >
40 <span className="fd-shim-ring" aria-hidden="true" />
41 <span className="fd-shim-sweep" aria-hidden="true" />
42 <span className="fd-shim-content">
43 <span className="fd-shim-label">{children}</span>
44 {iconRight && <span className="fd-shim-icon">{iconRight}</span>}
45 </span>
46 </button>
47 </>
48 );
49});
50
51const css = `
52:where(.fd-shim,.fd-shim *){box-sizing:border-box;margin:0;padding:0;font:inherit;letter-spacing:inherit;color:inherit;text-decoration:none;list-style:none}
53:where(.fd-shim,.fd-shim *)::before,:where(.fd-shim,.fd-shim *)::after{box-sizing:border-box}
54:where(.fd-shim svg){display:block;flex-shrink:0}
55@property --fd-shim-a{syntax:"<angle>";inherits:false;initial-value:0deg}
56.fd-shim{
57 --fd-ease-out:cubic-bezier(.22,1,.36,1);--fd-spring:cubic-bezier(.34,1.56,.64,1);--fd-dur-1:120ms;--fd-dur-2:240ms;--fd-dur-3:420ms;
58 --fd-shim-bg:#0B0B0F;--fd-shim-fg:#F5F5F7;--fd-shim-edge:rgba(255,255,255,.14);--fd-shim-glow:255,255,255;--fd-shim-radius:9999px;
59 --s-shadow:0 1px 0 rgba(255,255,255,.1) inset,0 1px 2px rgba(11,11,15,.3),0 12px 28px -12px rgba(11,11,15,.55);
60 position:relative;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;overflow:hidden;isolation:isolate;
61 appearance:none;-webkit-appearance:none;border:0;outline:0;cursor:pointer;user-select:none;-webkit-tap-highlight-color:transparent;
62 background:var(--fd-shim-bg);color:var(--fd-shim-fg);border-radius:var(--fd-shim-radius);
63 font:500 15px/1 ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif;letter-spacing:-.005em;
64 box-shadow:0 0 0 1px var(--fd-shim-edge),var(--s-shadow);
65 transform:scale(1);transition:transform var(--fd-dur-3) var(--fd-spring),box-shadow var(--fd-dur-2) var(--fd-ease-out);
66}
67[data-fd-theme="dark"] .fd-shim,.fd-shim[data-fd-theme="dark"]{--fd-shim-bg:#1F1F27;--fd-shim-edge:rgba(255,255,255,.16);--s-shadow:0 1px 0 rgba(255,255,255,.08) inset,0 1px 2px rgba(0,0,0,.5),0 12px 32px -12px rgba(0,0,0,.8)}
68.fd-shim[data-size="sm"]{height:32px;padding:0 14px;font-size:13px}
69.fd-shim[data-size="md"]{height:40px;padding:0 20px;font-size:15px}
70.fd-shim[data-size="lg"]{height:52px;padding:0 28px;font-size:17px;letter-spacing:-.01em}
71.fd-shim-content{position:relative;z-index:2;display:inline-flex;align-items:center;gap:8px}
72.fd-shim-label{display:block;white-space:nowrap}
73.fd-shim-icon{display:grid;place-items:center;width:18px;height:18px;transition:transform var(--fd-dur-2) var(--fd-ease-out)}
74.fd-shim-icon svg{width:100%;height:100%}
75.fd-shim-sweep{position:absolute;z-index:1;top:0;bottom:0;left:0;width:45%;pointer-events:none;
76 background:linear-gradient(100deg,transparent 0%,rgba(var(--fd-shim-glow),.0) 20%,rgba(var(--fd-shim-glow),.22) 50%,rgba(var(--fd-shim-glow),0) 80%,transparent 100%);
77 transform:translateX(-120%) skewX(-18deg);animation:fd-shim-sweep var(--fd-shim-speed,3.2s) var(--fd-ease-out) infinite}
78.fd-shim-ring{position:absolute;inset:0;z-index:1;border-radius:inherit;padding:1px;pointer-events:none;
79 background:conic-gradient(from var(--fd-shim-a),transparent 0deg,transparent 250deg,rgba(var(--fd-shim-glow),.9) 320deg,transparent 360deg);
80 -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask:linear-gradient(#000 0 0) content-box exclude,linear-gradient(#000 0 0);
81 animation:fd-shim-spin calc(var(--fd-shim-speed,3.2s) * 1.6) linear infinite;opacity:.8;transition:opacity var(--fd-dur-2) var(--fd-ease-out)}
82@media (hover:hover){
83 .fd-shim:hover{box-shadow:0 0 0 1px rgba(var(--fd-shim-glow),.22),var(--s-shadow),0 0 28px -4px rgba(var(--fd-shim-glow),.28)}
84 .fd-shim:hover .fd-shim-ring{opacity:1}
85 .fd-shim:hover .fd-shim-icon{transform:translateX(2px)}
86}
87.fd-shim:active{transform:scale(.96);transition:transform var(--fd-dur-1) var(--fd-ease-out),box-shadow var(--fd-dur-2) var(--fd-ease-out)}
88.fd-shim:focus-visible{outline:2px solid rgba(10,132,255,.8);outline-offset:2px}
89.fd-shim:disabled{opacity:.5;cursor:not-allowed;transform:none}
90.fd-shim:disabled .fd-shim-sweep,.fd-shim:disabled .fd-shim-ring{animation:none;opacity:0}
91@keyframes fd-shim-sweep{0%{transform:translateX(-120%) skewX(-18deg)}55%,100%{transform:translateX(320%) skewX(-18deg)}}
92@keyframes fd-shim-spin{to{--fd-shim-a:360deg}}
93@media (prefers-reduced-motion:reduce){
94 .fd-shim,.fd-shim:active{transition:box-shadow var(--fd-dur-1) linear;transform:none}
95 .fd-shim-sweep{display:none}
96 .fd-shim-ring{animation:none;--fd-shim-a:300deg;opacity:.6}
97 .fd-shim-icon{transition:none}
98}
99@media (prefers-contrast:more){.fd-shim{box-shadow:0 0 0 1px currentColor}}
100`;
101
102/* ───────────────────────────── demo ───────────────────────────── */
103
104const arrow = (
105 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M13 6l6 6-6 6" /></svg>
106);
107const check = (
108 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12.5l4.5 4.5L19 7.5" /></svg>
109);
110
111export function Demo() {
112 const [theme, setTheme] = useState<"light" | "dark">("dark");
113 const [email, setEmail] = useState("");
114 const [done, setDone] = useState(false);
115 const valid = /^\S+@\S+\.\S+$/.test(email);
116 const [touched, setTouched] = useState(false);
117
118 return (
119 <div className="fd-shim-demo" data-fd-theme={theme}>
120 <style>{demoCss}</style>
121 <header className="fd-shim-demo-bar">
122 <div className="fd-shim-demo-brand"><span className="fd-shim-demo-mark" aria-hidden="true" />Lumen</div>
123 <nav className="fd-shim-demo-nav" aria-label="Primary">
124 <a href="#models">Models</a><a href="#pricing">Pricing</a><a href="#changelog">Changelog</a>
125 </nav>
126 <button
127 type="button"
128 className="fd-shim-demo-theme"
129 aria-label={theme === "dark" ? "Switch to light theme" : "Switch to dark theme"}
130 onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
131 >
132 {theme === "dark" ? (
133 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><circle cx="12" cy="12" r="4" /><path d="M12 2v2M12 20v2M2 12h2M20 12h2M5 5l1.5 1.5M17.5 17.5L19 19M5 19l1.5-1.5M17.5 6.5L19 5" /></svg>
134 ) : (
135 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5z" /></svg>
136 )}
137 </button>
138 </header>
139
140 <main className="fd-shim-demo-hero">
141 <p className="fd-shim-demo-pill"><span className="fd-shim-demo-dot" />Private beta · Q4 2026</p>
142 <h1 className="fd-shim-demo-h1">Research that reads<br />the whole literature.</h1>
143 <p className="fd-shim-demo-sub">Lumen reads 40 million papers, patents and trial registries, then drafts a cited brief in under two minutes.</p>
144
145 {done ? (
146 <div className="fd-shim-demo-done" role="status">
147 <span className="fd-shim-demo-done-ic">{check}</span>
148 <span>You’re <b>#12,409</b> on the list. We’ll email {email}.</span>
149 </div>
150 ) : (
151 <form
152 className="fd-shim-demo-form"
153 onSubmit={(e) => {
154 e.preventDefault();
155 setTouched(true);
156 if (valid) setDone(true);
157 }}
158 >
159 <label className="fd-shim-demo-field">
160 <span className="fd-shim-demo-sr">Work email</span>
161 <input type="email" placeholder="you@lab.org" value={email} onChange={(e) => setEmail(e.target.value)} autoComplete="email" aria-invalid={touched && !valid} aria-describedby="fd-shim-err" />
162 </label>
163 <ShimmerButton size="lg" type="submit" iconRight={arrow}>Join the waitlist</ShimmerButton>
164 </form>
165 )}
166 <p id="fd-shim-err" className="fd-shim-demo-err" role="alert">{touched && !valid && !done ? "Enter a valid work email to join." : ""}</p>
167 <p className="fd-shim-demo-proof">
168 <span className="fd-shim-demo-faces" aria-hidden="true"><i>A</i><i>M</i><i>J</i><i>L</i></span>
169 12,408 researchers from Broad, EMBL and ETH Zürich already waiting
170 </p>
171
172 <div className="fd-shim-demo-sizes" aria-label="Sizes">
173 <ShimmerButton size="sm">Request access</ShimmerButton>
174 <ShimmerButton size="md" iconRight={arrow}>Read the docs</ShimmerButton>
175 </div>
176 </main>
177 </div>
178 );
179}
180
181const demoCss = `
182:where(.fd-shim-demo,.fd-shim-demo *){box-sizing:border-box;margin:0;padding:0;font:inherit;letter-spacing:inherit;color:inherit;text-decoration:none;list-style:none}
183:where(.fd-shim-demo,.fd-shim-demo *)::before,:where(.fd-shim-demo,.fd-shim-demo *)::after{box-sizing:border-box}
184:where(.fd-shim-demo svg){display:block;flex-shrink:0}
185:where(.fd-shim-demo button,.fd-shim-demo input){appearance:none;-webkit-appearance:none;background:none;border:0;border-radius:0;outline:0}
186.fd-shim-demo{--d-font:ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif;--d-disp:ui-sans-serif,-apple-system,"SF Pro Display","Inter",sans-serif;
187 --fd-ease-out:cubic-bezier(.22,1,.36,1);--fd-dur-2:240ms;--fd-dur-4:700ms;
188 --d-bg:#F7F7F9;--d-ink:#0B0B0F;--d-mut:#5C5C68;--d-line:rgba(11,11,15,.1);--d-field:#fff;--d-grid:rgba(11,11,15,.05);--d-chip:#fff;
189 position:relative;min-height:100vh;overflow:hidden;background:var(--d-bg);color:var(--d-ink);font:400 15px/1.45 var(--d-font);-webkit-font-smoothing:antialiased;transition:background-color var(--fd-dur-2) var(--fd-ease-out)}
190.fd-shim-demo[data-fd-theme="dark"]{--d-bg:#09090C;--d-ink:#F5F5F7;--d-mut:#9A9AA6;--d-line:rgba(255,255,255,.1);--d-field:#131318;--d-grid:rgba(255,255,255,.045);--d-chip:#131318}
191.fd-shim-demo::before{content:"";position:absolute;inset:0;pointer-events:none;
192 background-image:linear-gradient(var(--d-grid) 1px,transparent 1px),linear-gradient(90deg,var(--d-grid) 1px,transparent 1px);background-size:56px 56px;
193 -webkit-mask-image:radial-gradient(60% 60% at 50% 38%,#000 0%,transparent 75%);mask-image:radial-gradient(60% 60% at 50% 38%,#000 0%,transparent 75%)}
194.fd-shim-demo-bar{position:relative;z-index:1;display:flex;align-items:center;justify-content:space-between;height:64px;padding:0 24px;max-width:1120px;margin:0 auto}
195.fd-shim-demo-brand{display:flex;align-items:center;gap:10px;font:600 17px/1 var(--d-font);letter-spacing:-.01em}
196.fd-shim-demo-mark{width:18px;height:18px;border-radius:50%;background:var(--d-ink);box-shadow:0 0 0 4px var(--d-line)}
197.fd-shim-demo-nav{display:flex;gap:28px;font:400 15px/1 var(--d-font);color:var(--d-mut)}
198.fd-shim-demo-nav a{padding:8px 0;transition:color var(--fd-dur-2) var(--fd-ease-out)}
199.fd-shim-demo-nav a:hover{color:var(--d-ink)}
200.fd-shim-demo-nav a:focus-visible,.fd-shim-demo-theme:focus-visible{outline:2px solid rgba(10,132,255,.7);outline-offset:2px;border-radius:6px}
201.fd-shim-demo-theme{display:grid;place-items:center;width:36px;height:36px;border-radius:50%;cursor:pointer;color:var(--d-mut);transition:background-color var(--fd-dur-2) var(--fd-ease-out)}
202.fd-shim-demo-theme:hover{background:var(--d-line);color:var(--d-ink)}
203.fd-shim-demo-theme svg{width:18px;height:18px}
204.fd-shim-demo-hero{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;text-align:center;padding:clamp(56px,10vh,112px) 24px 64px;max-width:760px;margin:0 auto}
205.fd-shim-demo-pill{display:inline-flex;align-items:center;gap:8px;height:28px;padding:0 12px;border-radius:9999px;background:var(--d-chip);box-shadow:0 0 0 1px var(--d-line);font:500 13px/1 var(--d-font);color:var(--d-mut)}
206.fd-shim-demo-dot{width:6px;height:6px;border-radius:50%;background:#30D158;box-shadow:0 0 0 3px rgba(48,209,88,.2)}
207.fd-shim-demo-h1{margin-top:24px;font:600 clamp(36px,6.4vw,64px)/1.04 var(--d-disp);letter-spacing:-.04em}
208.fd-shim-demo-sub{margin-top:20px;max-width:540px;font:400 17px/1.5 var(--d-font);letter-spacing:-.01em;color:var(--d-mut)}
209.fd-shim-demo-form{display:flex;gap:8px;margin-top:36px;width:100%;max-width:520px;align-items:center}
210.fd-shim-demo-field{flex:1;min-width:0}
211.fd-shim-demo-field input{width:100%;height:52px;padding:0 20px;border-radius:9999px;background:var(--d-field);color:var(--d-ink);font:400 15px/1 var(--d-font);box-shadow:0 0 0 1px var(--d-line),0 1px 2px rgba(0,0,0,.06) inset;transition:box-shadow var(--fd-dur-2) var(--fd-ease-out)}
212.fd-shim-demo-field input::placeholder{color:var(--d-mut);opacity:.8}
213.fd-shim-demo-field input[aria-invalid="true"]{box-shadow:0 0 0 1.5px #FF6A60}
214.fd-shim-demo-field input:focus-visible{box-shadow:0 0 0 2px rgba(10,132,255,.7)}
215.fd-shim-demo-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
216.fd-shim-demo-done{display:flex;align-items:center;gap:12px;margin-top:36px;height:52px;padding:0 24px 0 12px;border-radius:9999px;background:var(--d-field);box-shadow:0 0 0 1px var(--d-line);font:400 15px/1 var(--d-font);animation:fd-shim-in 420ms var(--fd-ease-out)}
217.fd-shim-demo-done b{font-weight:600;font-variant-numeric:tabular-nums}
218.fd-shim-demo-done-ic{display:grid;place-items:center;width:28px;height:28px;border-radius:50%;background:#30D158;color:#04210E}
219.fd-shim-demo-done-ic svg{width:16px;height:16px}
220.fd-shim-demo-err{height:20px;margin-top:10px;font:400 13px/20px var(--d-font);color:#FF6A60}
221.fd-shim-demo-proof{display:flex;align-items:center;gap:12px;margin-top:12px;font:400 13px/1.3 var(--d-font);color:var(--d-mut);text-align:left}
222.fd-shim-demo-faces{display:flex}
223.fd-shim-demo-faces i{display:grid;place-items:center;width:24px;height:24px;margin-left:-6px;border-radius:50%;background:var(--d-field);box-shadow:0 0 0 2px var(--d-bg),0 0 0 3px var(--d-line);font:600 9px/1 var(--d-font);font-style:normal;color:var(--d-mut)}
224.fd-shim-demo-faces i:first-child{margin-left:0}
225.fd-shim-demo-faces i:nth-child(2){background:#3A3A46;color:#fff}.fd-shim-demo-faces i:nth-child(3){background:#0A84FF;color:#fff}.fd-shim-demo-faces i:nth-child(4){background:#E8E8EC;color:#0B0B0F}
226.fd-shim-demo-sizes{display:flex;align-items:center;gap:16px;margin-top:64px;padding-top:32px;border-top:1px solid var(--d-line);width:100%;justify-content:center}
227@keyframes fd-shim-in{from{opacity:0;transform:translateY(8px) scale(.98)}}
228@media (max-width:640px){.fd-shim-demo-nav{display:none}.fd-shim-demo-bar{padding:0 16px}.fd-shim-demo-hero{padding-inline:16px}.fd-shim-demo-form{flex-direction:column;align-items:stretch}.fd-shim-demo-form .fd-shim{width:100%}.fd-shim-demo-sizes{flex-wrap:wrap}.fd-shim-demo-proof{max-width:300px}}
229@media (prefers-reduced-motion:reduce){.fd-shim-demo-done{animation:none}}
230`;

More Buttons. Same file contract.

Button Core

The complete button system: 5 variants, 3 sizes, icon slots, loading → success morph and a springy press.

Magnetic Button

A button that leans toward your cursor on a spring, with the label drifting further than the shell.

Hold to Confirm

Press and hold to confirm dangerous actions. Progress fills through the button, early release cancels, Space works too.