Shimmer Button
A dark capsule with a travelling light sweep and a rotating border glow for your one important call to action.
The complete button system: 5 variants, 3 sizes, icon slots, loading → success morph and a springy press.
npx shadcn@latest add https://freedesignmd.com/r/button-core.json1/**2 * ButtonCore — the full button system.3 *4 * Variants: primary · secondary · tertiary · ghost · destructive5 * Sizes: sm (32) · md (40) · lg (48)6 * Icons: iconLeft · iconRight · icon-only (pass `icon` + `aria-label`)7 * States: hover · press (scale .96 + spring release) · focus-visible ·8 * disabled · loading → success check → idle9 *10 * Props: variant, size, shape ("pill" | "soft"), iconLeft, iconRight, icon,11 * loading (controlled), loadingLabel, successLabel, successDuration (ms),12 * theme ("light" | "dark", otherwise inherits from the nearest13 * [data-fd-theme] ancestor). `onClick` may return a Promise: the button then14 * morphs label → spinner → check → label by itself.15 * Wrap many buttons in <ButtonScope> to render the stylesheet only once.16 *17 * Browser support: all evergreen browsers. Needs React 18+ (SSR safe).18 * Accessibility: native <button>; aria-busy while loading, a polite live19 * region announces the success label; focus ring is never removed; while busy20 * the button stays focusable (aria-disabled) so focus is not lost.21 * Reduced motion: no scale/spring, spinner becomes a gentle pulse.22 */23import { createContext, forwardRef, useCallback, useContext, useEffect, useRef, useState } from "react";24import type { ButtonHTMLAttributes, MouseEvent, ReactNode } from "react";25 26export type ButtonVariant = "primary" | "secondary" | "tertiary" | "ghost" | "destructive";27export type ButtonSize = "sm" | "md" | "lg";28type Phase = "idle" | "loading" | "success";29 30export interface ButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onClick"> {31 variant?: ButtonVariant;32 size?: ButtonSize;33 shape?: "pill" | "soft";34 iconLeft?: ReactNode;35 iconRight?: ReactNode;36 /** Icon-only button. Always pair with aria-label. */37 icon?: ReactNode;38 loading?: boolean;39 loadingLabel?: ReactNode;40 successLabel?: ReactNode;41 successDuration?: number;42 theme?: "light" | "dark";43 onClick?: (e: MouseEvent<HTMLButtonElement>) => void | Promise<unknown>;44}45 46const StyleCtx = createContext(false);47 48/** Renders the stylesheet once for every Button inside it. */49export function ButtonScope({ children }: { children: ReactNode }) {50 return (51 <StyleCtx.Provider value>52 <style>{css}</style>53 {children}54 </StyleCtx.Provider>55 );56}57 58export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(59 {60 variant = "primary",61 size = "md",62 shape = "pill",63 iconLeft,64 iconRight,65 icon,66 loading,67 loadingLabel,68 successLabel,69 successDuration = 1600,70 theme,71 onClick,72 children,73 className,74 type = "button",75 ...rest76 },77 ref,78) {79 const scoped = useContext(StyleCtx);80 const [phase, setPhase] = useState<Phase>("idle");81 const alive = useRef(true);82 useEffect(() => {83 alive.current = true;84 return () => {85 alive.current = false;86 };87 }, []);88 89 const shown: Phase = loading ? "loading" : phase;90 const busy = shown !== "idle";91 92 const handle = useCallback(93 async (e: MouseEvent<HTMLButtonElement>) => {94 if (busy || rest.disabled) {95 e.preventDefault();96 return;97 }98 const out = onClick?.(e);99 if (out && typeof (out as Promise<unknown>).then === "function") {100 setPhase("loading");101 try {102 await out;103 if (!alive.current) return;104 setPhase("success");105 window.setTimeout(() => alive.current && setPhase("idle"), successDuration);106 } catch {107 if (alive.current) setPhase("idle");108 }109 }110 },111 [busy, onClick, rest.disabled, successDuration],112 );113 114 return (115 <>116 {!scoped && <style>{css}</style>}117 <button118 ref={ref}119 type={type}120 className={`fd-btn${className ? " " + className : ""}`}121 data-variant={variant}122 data-size={size}123 data-shape={shape}124 data-phase={shown}125 data-icon-only={icon ? "" : undefined}126 data-fd-theme={theme}127 aria-busy={shown === "loading" || undefined}128 aria-disabled={busy || undefined}129 onClick={handle}130 {...rest}131 >132 <span className="fd-btn-layer" data-layer="idle">133 {icon ? (134 <span className="fd-btn-icon">{icon}</span>135 ) : (136 <>137 {iconLeft && <span className="fd-btn-icon">{iconLeft}</span>}138 <span className="fd-btn-label">{children}</span>139 {iconRight && <span className="fd-btn-icon">{iconRight}</span>}140 </>141 )}142 </span>143 <span className="fd-btn-layer" data-layer="loading" aria-hidden="true">144 <Spinner />145 {loadingLabel && !icon && <span className="fd-btn-label">{loadingLabel}</span>}146 </span>147 <span className="fd-btn-layer" data-layer="success" aria-hidden="true">148 <Check />149 {successLabel && !icon && <span className="fd-btn-label">{successLabel}</span>}150 </span>151 <span className="fd-btn-sr" role="status" aria-live="polite">152 {shown === "success" ? (typeof successLabel === "string" ? successLabel : "Done") : ""}153 </span>154 </button>155 </>156 );157});158 159function Spinner() {160 return (161 <svg className="fd-btn-spin" width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true">162 <circle cx="12" cy="12" r="9" stroke="currentColor" strokeOpacity=".25" strokeWidth="2.5" />163 <path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" />164 </svg>165 );166}167 168function Check() {169 return (170 <svg className="fd-btn-check" width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true">171 <path d="M5 12.5l4.5 4.5L19 7.5" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />172 </svg>173 );174}175 176/* ───────────────────────────── styles ───────────────────────────── */177 178const css = `179:where(.fd-btn,.fd-btn *){box-sizing:border-box;margin:0;padding:0;font:inherit;letter-spacing:inherit;color:inherit;text-decoration:none;list-style:none}180:where(.fd-btn,.fd-btn *)::before,:where(.fd-btn,.fd-btn *)::after{box-sizing:border-box}181:where(.fd-btn svg){display:block;flex-shrink:0}182.fd-btn{183 --fd-ease-out:cubic-bezier(.22,1,.36,1);--fd-ease-in-out:cubic-bezier(.65,0,.35,1);--fd-spring:cubic-bezier(.34,1.56,.64,1);184 --fd-dur-1:120ms;--fd-dur-2:240ms;--fd-dur-3:420ms;185 --b-accent:#007AFF;--b-accent-hover:#1A8CFF;--b-accent-soft:rgba(0,122,255,.1);--b-accent-soft-hover:rgba(0,122,255,.16);--b-on-accent:#fff;186 --b-danger:#E5334B;--b-danger-hover:#EF4A60;--b-success:#1FA45B;187 --b-surface:#fff;--b-surface-hover:#F7F7F9;--b-border:rgba(11,11,15,.12);--b-ink:#0B0B0F;--b-ghost-hover:rgba(11,11,15,.06);188 --b-shadow:0 1px 2px rgba(11,11,15,.06),0 1px 1px rgba(11,11,15,.04);189 --b-inset:0 1px 0 rgba(255,255,255,.22) inset;190 --b-ring:rgba(0,122,255,.7);191 position:relative;display:inline-grid;place-items:center;flex-shrink:0;192 appearance:none;-webkit-appearance:none;border:0;outline:0;cursor:pointer;user-select:none;-webkit-tap-highlight-color:transparent;193 font:500 15px/1 ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif;letter-spacing:-.005em;194 height:var(--b-h);min-width:var(--b-h);padding:0 var(--b-px);border-radius:var(--b-r);195 background:var(--b-bg);color:var(--b-fg);box-shadow:var(--b-sh);196 transform:scale(1);197 transition:transform var(--fd-dur-3) var(--fd-spring),background-color var(--fd-dur-1) var(--fd-ease-out),box-shadow var(--fd-dur-2) var(--fd-ease-out),color var(--fd-dur-2) var(--fd-ease-out);198 white-space:nowrap;199}200[data-fd-theme="dark"] .fd-btn,.fd-btn[data-fd-theme="dark"]{201 --b-accent:#0A84FF;--b-accent-hover:#3B9CFF;--b-accent-soft:rgba(10,132,255,.16);--b-accent-soft-hover:rgba(10,132,255,.24);202 --b-danger:#FF453A;--b-danger-hover:#FF6A60;--b-success:#30D158;203 --b-surface:#1C1C22;--b-surface-hover:#25252C;--b-border:rgba(255,255,255,.1);--b-ink:#F5F5F7;--b-ghost-hover:rgba(255,255,255,.08);204 --b-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 1px 2px rgba(0,0,0,.3);205 --b-inset:0 1px 0 rgba(255,255,255,.28) inset;--b-ring:rgba(10,132,255,.8);206}207.fd-btn[data-fd-theme="light"]{--b-accent:#007AFF;--b-surface:#fff;--b-border:rgba(11,11,15,.12);--b-ink:#0B0B0F}208.fd-btn[data-size="sm"]{--b-h:32px;--b-px:12px;--b-gap:6px;--b-fs:13px;--b-ic:16px}209.fd-btn[data-size="md"]{--b-h:40px;--b-px:16px;--b-gap:8px;--b-fs:15px;--b-ic:18px}210.fd-btn[data-size="lg"]{--b-h:48px;--b-px:22px;--b-gap:10px;--b-fs:17px;--b-ic:20px;letter-spacing:-.01em}211.fd-btn[data-size="sm"]{font-size:13px}.fd-btn[data-size="md"]{font-size:15px}.fd-btn[data-size="lg"]{font-size:17px}212.fd-btn[data-shape="pill"]{--b-r:9999px}213.fd-btn[data-shape="soft"][data-size="sm"]{--b-r:8px}.fd-btn[data-shape="soft"][data-size="md"]{--b-r:10px}.fd-btn[data-shape="soft"][data-size="lg"]{--b-r:12px}214.fd-btn[data-icon-only]{padding:0}215 216.fd-btn[data-variant="primary"]{--b-bg:var(--b-accent);--b-fg:var(--b-on-accent);--b-sh:var(--b-inset),0 1px 2px rgba(0,40,120,.35),0 8px 18px -10px var(--b-accent)}217.fd-btn[data-variant="secondary"]{--b-bg:var(--b-surface);--b-fg:var(--b-ink);--b-sh:0 0 0 1px var(--b-border),var(--b-shadow)}218.fd-btn[data-variant="tertiary"]{--b-bg:var(--b-accent-soft);--b-fg:var(--b-accent);--b-sh:none}219.fd-btn[data-variant="ghost"]{--b-bg:transparent;--b-fg:var(--b-ink);--b-sh:none}220.fd-btn[data-variant="destructive"]{--b-bg:var(--b-danger);--b-fg:#fff;--b-sh:var(--b-inset),0 1px 2px rgba(120,0,20,.35),0 8px 18px -10px var(--b-danger)}221 222@media (hover:hover){223 .fd-btn[data-variant="primary"]:hover{--b-bg:var(--b-accent-hover)}224 .fd-btn[data-variant="secondary"]:hover{--b-bg:var(--b-surface-hover)}225 .fd-btn[data-variant="tertiary"]:hover{--b-bg:var(--b-accent-soft-hover)}226 .fd-btn[data-variant="ghost"]:hover{--b-bg:var(--b-ghost-hover)}227 .fd-btn[data-variant="destructive"]:hover{--b-bg:var(--b-danger-hover)}228}229.fd-btn:active{transform:scale(.96);transition:transform var(--fd-dur-1) var(--fd-ease-out),background-color var(--fd-dur-1) var(--fd-ease-out)}230.fd-btn:focus-visible{outline:2px solid var(--b-ring);outline-offset:2px}231.fd-btn:disabled{opacity:.4;cursor:not-allowed;transform:none;box-shadow:none}232.fd-btn:disabled:hover{--b-bg:var(--b-bg)}233.fd-btn[aria-disabled="true"]{cursor:default}234.fd-btn[data-phase="success"][data-variant="primary"],.fd-btn[data-phase="success"][data-variant="destructive"]{--b-bg:var(--b-success);--b-sh:var(--b-inset),0 1px 2px rgba(0,60,20,.35),0 8px 18px -10px var(--b-success)}235.fd-btn[data-phase="success"][data-variant="secondary"],.fd-btn[data-phase="success"][data-variant="tertiary"],.fd-btn[data-phase="success"][data-variant="ghost"]{--b-fg:var(--b-success)}236 237.fd-btn-layer{grid-area:1/1;display:inline-flex;align-items:center;justify-content:center;gap:var(--b-gap);238 transition:opacity var(--fd-dur-2) var(--fd-ease-in-out),transform var(--fd-dur-3) var(--fd-ease-out);}239.fd-btn-layer[data-layer="idle"]{opacity:1;transform:none}240.fd-btn-layer[data-layer="loading"],.fd-btn-layer[data-layer="success"]{opacity:0;transform:translateY(8px) scale(.85);pointer-events:none}241.fd-btn[data-phase="loading"] .fd-btn-layer[data-layer="idle"],.fd-btn[data-phase="success"] .fd-btn-layer[data-layer="idle"]{opacity:0;transform:translateY(-8px) scale(.9)}242.fd-btn[data-phase="loading"] .fd-btn-layer[data-layer="loading"],.fd-btn[data-phase="success"] .fd-btn-layer[data-layer="success"]{opacity:1;transform:none}243.fd-btn-label{display:block;line-height:1}244.fd-btn-icon{display:grid;place-items:center;width:var(--b-ic);height:var(--b-ic)}245.fd-btn-icon svg{width:100%;height:100%}246.fd-btn-spin,.fd-btn-check{width:var(--b-ic);height:var(--b-ic)}247.fd-btn-spin{animation:fd-btn-rot 800ms linear infinite}248.fd-btn-check path{stroke-dasharray:24;stroke-dashoffset:24}249.fd-btn[data-phase="success"] .fd-btn-check path{animation:fd-btn-draw 380ms var(--fd-ease-out) 120ms forwards}250.fd-btn-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}251@keyframes fd-btn-rot{to{transform:rotate(360deg)}}252@keyframes fd-btn-draw{to{stroke-dashoffset:0}}253@media (prefers-reduced-motion:reduce){254 .fd-btn,.fd-btn:active{transition:background-color var(--fd-dur-1) linear;transform:none}255 .fd-btn-layer{transition:opacity 100ms linear;transform:none!important}256 .fd-btn-spin{animation:fd-btn-pulse 1.4s ease-in-out infinite}257 .fd-btn[data-phase="success"] .fd-btn-check path{animation:none;stroke-dashoffset:0}258}259@keyframes fd-btn-pulse{50%{opacity:.35}}260@media (prefers-contrast:more){.fd-btn[data-variant="tertiary"],.fd-btn[data-variant="ghost"]{box-shadow:0 0 0 1px currentColor}}261`;262 263/* ───────────────────────────── demo ───────────────────────────── */264 265const I = {266 lock: (267 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="5" y="11" width="14" height="9" rx="2.5" /><path d="M8 11V8a4 4 0 0 1 8 0v3" /></svg>268 ),269 arrow: (270 <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>271 ),272 plus: (273 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M12 5v14M5 12h14" /></svg>274 ),275 trash: (276 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 7h16M10 11v6M14 11v6M6 7l1 12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-12M9 7V4h6v3" /></svg>277 ),278 share: (279 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 4v12M7 9l5-5 5 5M5 14v4a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4" /></svg>280 ),281 card: (282 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="5" width="18" height="14" rx="3" /><path d="M3 10h18M7 15h3" /></svg>283 ),284 bank: (285 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 10l9-6 9 6M5 10v8M9.5 10v8M14.5 10v8M19 10v8M3 20h18" /></svg>286 ),287 sun: (288 <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>289 ),290 moon: (291 <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>292 ),293};294 295const sizes: ButtonSize[] = ["sm", "md", "lg"];296const variants: ButtonVariant[] = ["primary", "secondary", "tertiary", "ghost", "destructive"];297 298export function Demo() {299 const [theme, setTheme] = useState<"light" | "dark">("light");300 const [method, setMethod] = useState<"card" | "bank">("card");301 const wait = (ms: number) => new Promise((r) => window.setTimeout(r, ms));302 303 return (304 <div className="fd-btn-demo" data-fd-theme={theme}>305 <style>{demoCss}</style>306 <ButtonScope>307 <header className="fd-btn-demo-bar">308 <div className="fd-btn-demo-brand">309 <span className="fd-btn-demo-logo" aria-hidden="true" />310 Halden Pay311 </div>312 <Button313 variant="ghost"314 size="sm"315 icon={theme === "light" ? I.moon : I.sun}316 aria-label={theme === "light" ? "Switch to dark theme" : "Switch to light theme"}317 onClick={() => setTheme(theme === "light" ? "dark" : "light")}318 />319 </header>320 321 <main className="fd-btn-demo-main">322 <section className="fd-btn-demo-card" aria-labelledby="fd-btn-pay-h">323 <p className="fd-btn-demo-eyebrow">Invoice INV-2041 · due 4 Nov</p>324 <h2 id="fd-btn-pay-h" className="fd-btn-demo-h">Pay Northwind Freight</h2>325 <p className="fd-btn-demo-amount">$2,480<span>.00</span></p>326 327 <dl className="fd-btn-demo-lines">328 <div><dt>Pallet storage · Oct</dt><dd>$1,920.00</dd></div>329 <div><dt>Cross-dock handling</dt><dd>$480.00</dd></div>330 <div><dt>Processing fee</dt><dd>$80.00</dd></div>331 </dl>332 333 <div className="fd-btn-demo-methods" role="radiogroup" aria-label="Payment method">334 {([335 ["card", "Visa ending 4242", "Expires 08/28", I.card],336 ["bank", "Chase checking ···· 0917", "ACH · free, 1–2 days", I.bank],337 ] as const).map(([id, t, s, ic]) => (338 <button key={id} type="button" role="radio" aria-checked={method === id} className="fd-btn-demo-method" onClick={() => setMethod(id)}>339 <span className="fd-btn-demo-method-ic">{ic}</span>340 <span className="fd-btn-demo-method-t">{t}<small>{s}</small></span>341 <span className="fd-btn-demo-radio" aria-hidden="true" />342 </button>343 ))}344 </div>345 346 <Button347 size="lg"348 variant="primary"349 iconLeft={I.lock}350 loadingLabel="Processing"351 successLabel="Payment sent"352 onClick={() => wait(1800)}353 style={{ width: "100%" }}354 >355 Pay $2,480.00356 </Button>357 <div className="fd-btn-demo-row">358 <Button variant="secondary" size="md" style={{ flex: 1 }}>Save for later</Button>359 <Button variant="ghost" size="md" style={{ flex: 1 }}>Cancel</Button>360 </div>361 <p className="fd-btn-demo-fine">Payments are encrypted. Funds settle in 1–2 business days.</p>362 </section>363 364 <section className="fd-btn-demo-card fd-btn-demo-spec" aria-labelledby="fd-btn-spec-h">365 <p className="fd-btn-demo-eyebrow">Button system</p>366 <h2 id="fd-btn-spec-h" className="fd-btn-demo-h">5 variants, 3 sizes</h2>367 368 <div className="fd-btn-demo-matrix">369 {variants.map((v) => (370 <div className="fd-btn-demo-mrow" key={v}>371 <span className="fd-btn-demo-mlabel">{v}</span>372 <div className="fd-btn-demo-mcells">373 {sizes.map((s) => (374 <Button key={s} variant={v} size={s}>375 {v === "destructive" ? "Delete" : v === "primary" ? "Continue" : "Label"}376 </Button>377 ))}378 </div>379 </div>380 ))}381 </div>382 383 <div className="fd-btn-demo-divider" />384 <div className="fd-btn-demo-mrow">385 <span className="fd-btn-demo-mlabel">icons</span>386 <div className="fd-btn-demo-mcells">387 <Button variant="secondary" iconLeft={I.plus}>New invoice</Button>388 <Button variant="primary" iconRight={I.arrow}>Next</Button>389 <Button variant="secondary" icon={I.share} aria-label="Share" />390 <Button variant="destructive" size="sm" icon={I.trash} aria-label="Delete" />391 </div>392 </div>393 <div className="fd-btn-demo-mrow">394 <span className="fd-btn-demo-mlabel">states</span>395 <div className="fd-btn-demo-mcells">396 <Button variant="primary" disabled>Disabled</Button>397 <Button variant="secondary" loading loadingLabel="Loading" />398 <Button variant="primary" shape="soft" onClick={() => wait(1200)} successLabel="Saved" loadingLabel="Saving">Save changes</Button>399 </div>400 </div>401 </section>402 </main>403 </ButtonScope>404 </div>405 );406}407 408const demoCss = `409:where(.fd-btn-demo,.fd-btn-demo *){box-sizing:border-box;margin:0;padding:0;font:inherit;letter-spacing:inherit;color:inherit;text-decoration:none;list-style:none}410:where(.fd-btn-demo,.fd-btn-demo *)::before,:where(.fd-btn-demo,.fd-btn-demo *)::after{box-sizing:border-box}411:where(.fd-btn-demo svg){display:block;flex-shrink:0}412:where(.fd-btn-demo button){appearance:none;-webkit-appearance:none;background:none;border:0;border-radius:0}413.fd-btn-demo{--d-font:ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif;--d-bg:#F7F7F9;--d-card:#fff;--d-ink:#0B0B0F;--d-mut:#6B6B76;--d-line:rgba(11,11,15,.08);--d-soft:#F1F1F4;--d-accent:#007AFF;414 --fd-ease-out:cubic-bezier(.22,1,.36,1);--fd-dur-2:240ms;415 min-height:100vh;background:var(--d-bg);color:var(--d-ink);font:400 15px/1.45 ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif;416 -webkit-font-smoothing:antialiased;transition:background-color var(--fd-dur-2) var(--fd-ease-out)}417.fd-btn-demo[data-fd-theme="dark"]{--d-bg:#0B0B0F;--d-card:#141418;--d-ink:#F5F5F7;--d-mut:#9A9AA6;--d-line:rgba(255,255,255,.08);--d-soft:#1C1C22;--d-accent:#0A84FF}418.fd-btn-demo-bar{display:flex;align-items:center;justify-content:space-between;height:56px;padding:0 24px;border-bottom:1px solid var(--d-line)}419.fd-btn-demo-brand{display:flex;align-items:center;gap:10px;font:600 15px/1 var(--d-font);letter-spacing:-.01em}420.fd-btn-demo-logo{width:20px;height:20px;border-radius:6px;background:var(--d-ink);position:relative}421.fd-btn-demo-logo::after{content:"";position:absolute;inset:6px;border-radius:2px;background:var(--d-bg)}422.fd-btn-demo-main{display:grid;grid-template-columns:minmax(0,420px) minmax(0,520px);gap:24px;justify-content:center;align-items:start;padding:48px 24px 64px}423.fd-btn-demo-card{display:flex;flex-direction:column;gap:16px;padding:28px;border-radius:24px;background:var(--d-card);424 box-shadow:0 0 0 1px var(--d-line),0 1px 2px rgba(0,0,0,.04),0 24px 48px -24px rgba(0,0,0,.18)}425.fd-btn-demo[data-fd-theme="dark"] .fd-btn-demo-card{box-shadow:0 0 0 1px var(--d-line),0 1px 0 rgba(255,255,255,.04) inset,0 24px 48px -24px rgba(0,0,0,.6)}426.fd-btn-demo-eyebrow{font:500 13px/1 var(--d-font);color:var(--d-mut);letter-spacing:0}427.fd-btn-demo-h{font:600 20px/1.2 var(--d-font);letter-spacing:-.01em}428.fd-btn-demo-amount{font:600 44px/1 ui-sans-serif,-apple-system,"SF Pro Display","Inter",sans-serif;letter-spacing:-.03em;font-variant-numeric:tabular-nums;margin:4px 0 4px}429.fd-btn-demo-amount span{color:var(--d-mut)}430.fd-btn-demo-lines{display:flex;flex-direction:column;gap:12px;padding:16px 0;border-block:1px solid var(--d-line);font-size:15px}431.fd-btn-demo-lines div{display:flex;justify-content:space-between;gap:16px}432.fd-btn-demo-lines dt{color:var(--d-mut)}433.fd-btn-demo-lines dd{font-variant-numeric:tabular-nums}434.fd-btn-demo-methods{display:flex;flex-direction:column;gap:8px}435.fd-btn-demo-method{display:flex;align-items:center;gap:12px;width:100%;padding:12px 14px;border-radius:14px;text-align:left;cursor:pointer;background:var(--d-card);436 box-shadow:0 0 0 1px var(--d-line);transition:box-shadow var(--fd-dur-2) var(--fd-ease-out),background-color var(--fd-dur-2) var(--fd-ease-out);font:inherit;color:inherit}437.fd-btn-demo-method:focus-visible{outline:2px solid color-mix(in srgb,var(--d-accent) 70%,transparent);outline-offset:2px}438.fd-btn-demo-method[aria-checked="true"]{box-shadow:0 0 0 1.5px var(--d-accent);background:color-mix(in srgb,var(--d-accent) 5%,var(--d-card))}439.fd-btn-demo-method-ic{display:grid;place-items:center;width:36px;height:36px;border-radius:10px;background:var(--d-soft)}440.fd-btn-demo-method-ic svg{width:18px;height:18px}441.fd-btn-demo-method-t{display:flex;flex-direction:column;gap:2px;flex:1;font:500 15px/1.2 var(--d-font)}442.fd-btn-demo-method-t small{font:400 13px/1.2 var(--d-font);color:var(--d-mut)}443.fd-btn-demo-radio{width:18px;height:18px;border-radius:50%;box-shadow:0 0 0 1.5px var(--d-line) inset,0 0 0 1px var(--d-mut) inset;opacity:.6;transition:all var(--fd-dur-2) var(--fd-ease-out)}444.fd-btn-demo-method[aria-checked="true"] .fd-btn-demo-radio{opacity:1;box-shadow:0 0 0 5px var(--d-accent) inset}445.fd-btn-demo-row{display:flex;gap:8px}446.fd-btn-demo-fine{font:400 13px/1.4 var(--d-font);color:var(--d-mut);text-align:center}447.fd-btn-demo-matrix{display:flex;flex-direction:column;gap:16px}448.fd-btn-demo-mrow{display:grid;grid-template-columns:84px 1fr;align-items:center;gap:12px}449.fd-btn-demo-mlabel{font:500 12px/1 ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace;color:var(--d-mut)}450.fd-btn-demo-mcells{display:flex;flex-wrap:wrap;align-items:center;gap:12px}451.fd-btn-demo-divider{height:1px;background:var(--d-line);margin:4px 0}452@media (max-width:900px){.fd-btn-demo-main{grid-template-columns:minmax(0,1fr);padding:24px 16px 48px}.fd-btn-demo-bar{padding:0 16px}}453@media (max-width:520px){.fd-btn-demo-card{padding:20px;border-radius:20px}.fd-btn-demo-mrow{grid-template-columns:1fr;gap:8px}}454@media (prefers-reduced-motion:reduce){.fd-btn-demo,.fd-btn-demo *{transition-duration:.01ms!important}}455`;A dark capsule with a travelling light sweep and a rotating border glow for your one important call to action.
A button that leans toward your cursor on a spring, with the label drifting further than the shell.
Press and hold to confirm dangerous actions. Progress fills through the button, early release cancels, Space works too.