Button Core
The complete button system: 5 variants, 3 sizes, icon slots, loading → success morph and a springy press.
An accessible toolbar of icon toggles and actions with warm tooltips, shortcuts and roving focus.
Browser support · All evergreen browsers. Tooltip pairing uses :has() with a graceful fallback.
npx shadcn@latest add https://freedesignmd.com/r/icon-button-group.json1/**2 * IconButtonGroup — a toolbar of icon buttons with tooltips, toggle3 * (aria-pressed) states, exclusive sub-groups and separators.4 *5 * Props: groups: ToolItem[][] — each inner array is a visual group with a6 * separator between groups. ToolItem = { id, label, icon, shortcut?,7 * kind?: "toggle" | "action", exclusive?: string, disabled? }.8 * pressed (Record<id, boolean>) + onToggle(id, next) for toggles,9 * onAction(id) for actions, size ("sm" | "md"), aria-label, theme.10 * Items sharing the same `exclusive` key behave like a radio set.11 *12 * Browser support: all evergreen browsers.13 * Accessibility: role="toolbar" with a roving tabindex (ArrowLeft/Right,14 * Home/End); toggles expose aria-pressed; every button has an aria-label and15 * aria-keyshortcuts; tooltips (role="tooltip") appear on hover after 400ms16 * (instantly once warm) and on keyboard focus, and close on Escape.17 * Reduced motion: tooltip fades only.18 */19import { useEffect, useId, useRef, useState } from "react";20import type { KeyboardEvent, ReactNode } from "react";21 22export interface ToolItem {23 id: string;24 label: string;25 icon: ReactNode;26 shortcut?: string;27 kind?: "toggle" | "action";28 exclusive?: string;29 disabled?: boolean;30}31 32export interface IconButtonGroupProps {33 groups: ToolItem[][];34 pressed?: Record<string, boolean>;35 onToggle?: (id: string, next: boolean) => void;36 onAction?: (id: string) => void;37 size?: "sm" | "md";38 theme?: "light" | "dark";39 className?: string;40 "aria-label"?: string;41}42 43export function IconButtonGroup({44 groups,45 pressed = {},46 onToggle,47 onAction,48 size = "md",49 theme,50 className,51 "aria-label": ariaLabel = "Formatting",52}: IconButtonGroupProps) {53 const uid = useId();54 const bar = useRef<HTMLDivElement>(null);55 const flat = groups.flat();56 const [active, setActive] = useState(flat[0]?.id);57 const [tip, setTip] = useState<{ id: string; x: number; y: number } | null>(null);58 const timer = useRef(0);59 const warm = useRef(0);60 61 useEffect(() => () => window.clearTimeout(timer.current), []);62 63 const showTip = (id: string, el: HTMLElement, delay: number) => {64 window.clearTimeout(timer.current);65 const go = () => setTip({ id, x: el.offsetLeft + el.offsetWidth / 2, y: el.offsetTop });66 if (delay <= 0 || Date.now() - warm.current < 600) go();67 else timer.current = window.setTimeout(go, delay);68 };69 const hideTip = () => {70 window.clearTimeout(timer.current);71 setTip((t) => {72 if (t) warm.current = Date.now();73 return null;74 });75 };76 77 const onKey = (e: KeyboardEvent) => {78 if (e.key === "Escape") return hideTip();79 const btns = Array.from(bar.current?.querySelectorAll<HTMLButtonElement>("button:not(:disabled)") ?? []);80 const i = btns.indexOf(document.activeElement as HTMLButtonElement);81 let n = -1;82 if (e.key === "ArrowRight") n = (i + 1) % btns.length;83 else if (e.key === "ArrowLeft") n = (i - 1 + btns.length) % btns.length;84 else if (e.key === "Home") n = 0;85 else if (e.key === "End") n = btns.length - 1;86 if (n >= 0) {87 e.preventDefault();88 btns[n].focus();89 }90 };91 92 const press = (it: ToolItem) => {93 if (it.kind === "action") return onAction?.(it.id);94 onToggle?.(it.id, !pressed[it.id]);95 };96 97 const tipItem = tip ? flat.find((f) => f.id === tip.id) : null;98 99 return (100 <div className={`fd-ibg${className ? " " + className : ""}`} data-size={size} data-fd-theme={theme}>101 <style>{css}</style>102 <div ref={bar} className="fd-ibg-bar" role="toolbar" aria-label={ariaLabel} onKeyDown={onKey}>103 {groups.map((g, gi) => (104 <div key={gi} className="fd-ibg-group" role="group">105 {g.map((it) => {106 const isToggle = it.kind !== "action";107 return (108 <button109 key={it.id}110 type="button"111 className="fd-ibg-btn"112 aria-label={it.label}113 aria-pressed={isToggle ? !!pressed[it.id] : undefined}114 aria-keyshortcuts={it.shortcut}115 aria-describedby={tip?.id === it.id ? `${uid}-tip` : undefined}116 disabled={it.disabled}117 tabIndex={active === it.id ? 0 : -1}118 onClick={() => {119 press(it);120 setActive(it.id);121 }}122 onFocus={(e) => {123 setActive(it.id);124 if (e.currentTarget.matches(":focus-visible")) showTip(it.id, e.currentTarget, 0);125 }}126 onBlur={hideTip}127 onPointerEnter={(e) => e.pointerType === "mouse" && showTip(it.id, e.currentTarget, 400)}128 onPointerLeave={hideTip}129 onPointerDown={hideTip}130 >131 <span className="fd-ibg-ic">{it.icon}</span>132 </button>133 );134 })}135 {gi < groups.length - 1 && <span className="fd-ibg-sep" aria-hidden="true" />}136 </div>137 ))}138 {tip && tipItem && (139 <div id={`${uid}-tip`} role="tooltip" className="fd-ibg-tip" style={{ left: tip.x, top: tip.y }}>140 {tipItem.label}141 {tipItem.shortcut && <kbd>{tipItem.shortcut}</kbd>}142 </div>143 )}144 </div>145 </div>146 );147}148 149const css = `150:where(.fd-ibg,.fd-ibg *){box-sizing:border-box;margin:0;padding:0;font:inherit;letter-spacing:inherit;color:inherit;text-decoration:none;list-style:none}151:where(.fd-ibg,.fd-ibg *)::before,:where(.fd-ibg,.fd-ibg *)::after{box-sizing:border-box}152:where(.fd-ibg svg){display:block;flex-shrink:0}153:where(.fd-ibg button){appearance:none;-webkit-appearance:none;background:none;border:0;border-radius:0}154.fd-ibg{155 --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;156 --i-bg:#fff;--i-fg:#0B0B0F;--i-mut:#5F5F6B;--i-hover:rgba(11,11,15,.06);--i-on:rgba(0,122,255,.12);--i-on-fg:#0058C7;--i-line:rgba(11,11,15,.1);--i-ring:rgba(0,122,255,.7);157 --i-tip-bg:#0B0B0F;--i-tip-fg:#F5F5F7;158 --i-sh:0 0 0 1px var(--i-line),0 1px 2px rgba(11,11,15,.05),0 8px 24px -12px rgba(11,11,15,.22);159 display:inline-block;max-width:100%;160 font:500 12px/1 ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif;color:var(--i-fg);161}162[data-fd-theme="dark"] .fd-ibg,.fd-ibg[data-fd-theme="dark"]{--i-bg:#1C1C22;--i-fg:#F5F5F7;--i-mut:#A1A1AD;--i-hover:rgba(255,255,255,.08);--i-on:rgba(10,132,255,.24);--i-on-fg:#6CB4FF;--i-line:rgba(255,255,255,.1);--i-ring:rgba(10,132,255,.85);--i-tip-bg:#F5F5F7;--i-tip-fg:#0B0B0F;163 --i-sh:0 0 0 1px var(--i-line),0 1px 0 rgba(255,255,255,.05) inset,0 8px 24px -12px rgba(0,0,0,.7)}164.fd-ibg[data-size="sm"]{--i-b:28px;--i-ic:16px}165.fd-ibg[data-size="md"]{--i-b:34px;--i-ic:18px}166.fd-ibg-bar{position:relative;display:inline-flex;align-items:center;gap:4px;max-width:100%;flex-wrap:wrap;padding:4px;border-radius:calc(var(--i-b)/3.4 + 8px);background:var(--i-bg);box-shadow:var(--i-sh)}167.fd-ibg-group{display:inline-flex;align-items:center;gap:2px}168.fd-ibg-sep{width:1px;height:18px;margin:0 4px 0 6px;background:var(--i-line)}169.fd-ibg-btn{position:relative;display:grid;place-items:center;width:var(--i-b);height:var(--i-b);border-radius:calc(var(--i-b)/3.4 + 4px);170 appearance:none;-webkit-appearance:none;border:0;outline:0;cursor:pointer;-webkit-tap-highlight-color:transparent;background:transparent;color:var(--i-mut);171 transform:scale(1);transition:transform 420ms var(--fd-spring),background-color var(--fd-dur-1) var(--fd-ease-out),color var(--fd-dur-1) var(--fd-ease-out)}172.fd-ibg-ic{display:grid;place-items:center;width:var(--i-ic);height:var(--i-ic)}.fd-ibg-ic svg{width:100%;height:100%}173@media (hover:hover){.fd-ibg-btn:hover:not(:disabled){background:var(--i-hover);color:var(--i-fg)}}174.fd-ibg-btn[aria-pressed="true"]{background:var(--i-on);color:var(--i-on-fg)}175.fd-ibg-btn:active:not(:disabled){transform:scale(.92);transition:transform var(--fd-dur-1) var(--fd-ease-out)}176.fd-ibg-btn:focus-visible{outline:2px solid var(--i-ring);outline-offset:1px}177.fd-ibg-btn:disabled{opacity:.35;cursor:not-allowed}178.fd-ibg-tip{position:absolute;z-index:10;transform:translate(-50%,calc(-100% - 10px));display:flex;align-items:center;gap:8px;padding:6px 8px 6px 10px;border-radius:8px;background:var(--i-tip-bg);color:var(--i-tip-fg);white-space:nowrap;pointer-events:none;179 font:500 12px/1 inherit;font-family:inherit;box-shadow:0 4px 12px rgba(0,0,0,.25);animation:fd-ibg-tip var(--fd-dur-2) var(--fd-ease-out)}180.fd-ibg-tip:not(:has(kbd)){padding-right:10px}181.fd-ibg-tip kbd{font:500 11px/1 ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace;padding:3px 5px;border-radius:5px;background:rgba(127,127,140,.28);color:inherit;opacity:.85}182.fd-ibg-tip::after{content:"";position:absolute;left:50%;top:100%;width:8px;height:8px;margin:-4px 0 0 -4px;background:var(--i-tip-bg);transform:rotate(45deg);border-radius:0 0 2px 0}183@keyframes fd-ibg-tip{from{opacity:0;transform:translate(-50%,calc(-100% - 6px)) scale(.96)}}184@media (prefers-reduced-motion:reduce){.fd-ibg-btn,.fd-ibg-btn:active:not(:disabled){transition:background-color 1ms;transform:none}.fd-ibg-tip{animation:fd-ibg-fade 100ms linear}}185@keyframes fd-ibg-fade{from{opacity:0}}186@media (prefers-contrast:more){.fd-ibg-btn[aria-pressed="true"]{box-shadow:0 0 0 1px currentColor}}187`;188 189/* ───────────────────────────── demo ───────────────────────────── */190 191const S = ({ children }: { children: ReactNode }) => (192 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">{children}</svg>193);194const icons = {195 bold: <S><path d="M7 5h6a3.5 3.5 0 0 1 0 7H7zM7 12h7a3.5 3.5 0 0 1 0 7H7z" /></S>,196 italic: <S><path d="M19 4h-9M14 20H5M15 4L9 20" /></S>,197 underline: <S><path d="M7 4v7a5 5 0 0 0 10 0V4M5 20h14" /></S>,198 strike: <S><path d="M4 12h16M16 6.5C15 5.5 13.7 5 12 5c-2.5 0-4 1.2-4 3 0 1.2.8 2 2 2.5M8 17.5c1 1 2.3 1.5 4 1.5 2.5 0 4-1.2 4-3 0-.5-.1-1-.4-1.4" /></S>,199 code: <S><path d="M8 7l-5 5 5 5M16 7l5 5-5 5M14 4l-4 16" /></S>,200 link: <S><path d="M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1" /></S>,201 bullets: <S><path d="M9 6h11M9 12h11M9 18h11M4.5 6h.01M4.5 12h.01M4.5 18h.01" /></S>,202 numbers: <S><path d="M10 6h10M10 12h10M10 18h10M4 5l1.5-1v4M3.8 13.5c.4-1 2.2-1 2.2.2 0 .8-2.2 1.6-2.2 2.8H6.2M4 19.5h1.7l-1 1.2c1-.2 1.6.3 1.6 1s-.8 1.1-2.3.8" /></S>,203 quote: <S><path d="M5 17c0-4 1-7 4-9M13 17c0-4 1-7 4-9M5 17h4v-4H5zM13 17h4v-4h-4z" /></S>,204 left: <S><path d="M4 6h16M4 10h10M4 14h16M4 18h10" /></S>,205 center: <S><path d="M4 6h16M7 10h10M4 14h16M7 18h10" /></S>,206 right: <S><path d="M4 6h16M10 10h10M4 14h16M10 18h10" /></S>,207 undo: <S><path d="M9 14L4 9l5-5M4 9h10a6 6 0 0 1 0 12h-3" /></S>,208 redo: <S><path d="M15 14l5-5-5-5M20 9H10a6 6 0 0 0 0 12h3" /></S>,209 clear: <S><path d="M5 5l14 14M9 4h10M14 4l-2 6M10 20h5l1-4" /></S>,210};211 212const isMac = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform);213 214export function Demo() {215 const [theme, setTheme] = useState<"light" | "dark">("light");216 const [pressed, setPressed] = useState<Record<string, boolean>>({ "align-left": true, bold: true });217 const [hist, setHist] = useState<Record<string, boolean>[]>([]);218 const [future, setFuture] = useState<Record<string, boolean>[]>([]);219 const [mod, setMod] = useState("Ctrl ");220 useEffect(() => setMod(isMac() ? "⌘ " : "Ctrl "), []);221 222 const groups: ToolItem[][] = [223 [224 { id: "bold", label: "Bold", icon: icons.bold, shortcut: `${mod}B` },225 { id: "italic", label: "Italic", icon: icons.italic, shortcut: `${mod}I` },226 { id: "underline", label: "Underline", icon: icons.underline, shortcut: `${mod}U` },227 { id: "strike", label: "Strikethrough", icon: icons.strike, shortcut: `${mod}⇧ X` },228 { id: "code", label: "Inline code", icon: icons.code, shortcut: `${mod}E` },229 ],230 [231 { id: "bullets", label: "Bulleted list", icon: icons.bullets, exclusive: "list" },232 { id: "numbers", label: "Numbered list", icon: icons.numbers, exclusive: "list" },233 { id: "quote", label: "Block quote", icon: icons.quote },234 { id: "link", label: "Link", icon: icons.link, shortcut: `${mod}K` },235 ],236 [237 { id: "align-left", label: "Align left", icon: icons.left, exclusive: "align" },238 { id: "align-center", label: "Align center", icon: icons.center, exclusive: "align" },239 { id: "align-right", label: "Align right", icon: icons.right, exclusive: "align" },240 ],241 [242 { id: "undo", label: "Undo", icon: icons.undo, kind: "action", shortcut: `${mod}Z`, disabled: hist.length === 0 },243 { id: "redo", label: "Redo", icon: icons.redo, kind: "action", shortcut: `${mod}⇧ Z`, disabled: future.length === 0 },244 { id: "clear", label: "Clear formatting", icon: icons.clear, kind: "action" },245 ],246 ];247 const flat = groups.flat();248 249 const commit = (next: Record<string, boolean>) => {250 setHist((h) => [...h, pressed].slice(-20));251 setFuture([]);252 setPressed(next);253 };254 const toggle = (id: string, on: boolean) => {255 const ex = flat.find((f) => f.id === id)?.exclusive;256 const next = { ...pressed };257 if (ex) flat.filter((f) => f.exclusive === ex).forEach((f) => (next[f.id] = false));258 if (ex === "align") next[id] = true; // alignment always has one active259 else next[id] = on;260 commit(next);261 };262 const act = (id: string) => {263 if (id === "undo" && hist.length) {264 setFuture((f) => [pressed, ...f]);265 setPressed(hist[hist.length - 1]);266 setHist((h) => h.slice(0, -1));267 } else if (id === "redo" && future.length) {268 setHist((h) => [...h, pressed]);269 setPressed(future[0]);270 setFuture((f) => f.slice(1));271 } else if (id === "clear") commit({ "align-left": true });272 };273 const onKeyDown = (e: React.KeyboardEvent) => {274 if (!(e.metaKey || e.ctrlKey)) return;275 const map: Record<string, string> = { b: "bold", i: "italic", u: "underline", e: "code", k: "link" };276 const id = map[e.key.toLowerCase()];277 if (id) {278 e.preventDefault();279 toggle(id, !pressed[id]);280 }281 };282 283 const align = pressed["align-center"] ? "center" : pressed["align-right"] ? "right" : "left";284 const marks = [pressed.bold && "b", pressed.italic && "i", pressed.underline && "u", pressed.strike && "s", pressed.code && "c", pressed.link && "l"].filter(Boolean).join(" ");285 const items = ["Net revenue retention reached 118%", "Median onboarding fell to 9 days", "Two enterprise renewals slipped to Q4"];286 const List = pressed.numbers ? "ol" : pressed.bullets ? "ul" : "div";287 288 return (289 <div className="fd-ibg-demo" data-fd-theme={theme}>290 <style>{demoCss}</style>291 <header className="fd-ibg-demo-bar">292 <span className="fd-ibg-demo-brand">Quill<b>Docs</b></span>293 <span className="fd-ibg-demo-doc">Board updates / <u>Q3 2026</u></span>294 <button type="button" className="fd-ibg-demo-theme" aria-label={theme === "dark" ? "Switch to light theme" : "Switch to dark theme"} onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>{theme === "dark" ? "Light" : "Dark"}</button>295 </header>296 297 <main className="fd-ibg-demo-main">298 <div className="fd-ibg-demo-toolbar">299 <IconButtonGroup groups={groups} pressed={pressed} onToggle={toggle} onAction={act} aria-label="Text formatting" />300 </div>301 <article className="fd-ibg-demo-page" onKeyDown={onKeyDown} tabIndex={0} aria-label="Document preview. Use Ctrl or Command with B, I, U to format.">302 <p className="fd-ibg-demo-kicker">Board update · Quarterly</p>303 <h1>Q3 2026: efficient growth, slower enterprise close</h1>304 <p className="fd-ibg-demo-meta">Priya Natarajan, CFO · Updated 27 Oct</p>305 <div className="fd-ibg-demo-body" style={{ textAlign: align }} data-quote={pressed.quote || undefined}>306 <p>307 ARR closed the quarter at $18.4M, and{" "}308 <span className="fd-ibg-demo-mark" data-marks={marks}>net new ARR grew 22% quarter over quarter</span>309 , driven by mid-market expansion in logistics and healthcare.310 </p>311 <List className="fd-ibg-demo-list" data-kind={pressed.numbers ? "ol" : pressed.bullets ? "ul" : "plain"}>312 {items.map((t) => (313 <li key={t}>{t}</li>314 ))}315 </List>316 </div>317 </article>318 <p className="fd-ibg-demo-hint">Hover or focus a tool for its tooltip. Arrow keys move through the toolbar.</p>319 </main>320 </div>321 );322}323 324const demoCss = `325:where(.fd-ibg-demo,.fd-ibg-demo *){box-sizing:border-box;margin:0;padding:0;font:inherit;letter-spacing:inherit;color:inherit;text-decoration:none;list-style:none}326:where(.fd-ibg-demo,.fd-ibg-demo *)::before,:where(.fd-ibg-demo,.fd-ibg-demo *)::after{box-sizing:border-box}327:where(.fd-ibg-demo button){appearance:none;-webkit-appearance:none;background:none;border:0;border-radius:0}328.fd-ibg-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;--d-mono:ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace;329 --fd-ease-out:cubic-bezier(.22,1,.36,1);--fd-dur-2:240ms;330 --d-bg:#F1F1F4;--d-page:#fff;--d-ink:#0B0B0F;--d-mut:#6B6B76;--d-line:rgba(11,11,15,.08);--d-accent:#007AFF;331 min-height:100vh;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)}332.fd-ibg-demo[data-fd-theme="dark"]{--d-bg:#09090B;--d-page:#131317;--d-ink:#F5F5F7;--d-mut:#9A9AA6;--d-line:rgba(255,255,255,.08);--d-accent:#0A84FF}333.fd-ibg-demo-bar{display:flex;align-items:center;justify-content:space-between;gap:16px;height:56px;padding:0 clamp(16px,4vw,32px);background:var(--d-page);border-bottom:1px solid var(--d-line);font:400 14px/1 var(--d-font);color:var(--d-mut)}334.fd-ibg-demo-brand{font:500 16px/1 var(--d-disp);letter-spacing:-.02em;color:var(--d-ink)}.fd-ibg-demo-brand b{font-weight:700;color:var(--d-accent)}335.fd-ibg-demo-doc u{text-decoration:none;color:var(--d-ink);font-weight:500}336.fd-ibg-demo-theme{cursor:pointer;height:32px;padding:0 14px;border-radius:9999px;box-shadow:0 0 0 1px var(--d-line);font:500 13px/1 var(--d-font);color:var(--d-mut)}337.fd-ibg-demo-theme:hover{color:var(--d-ink)}.fd-ibg-demo-theme:focus-visible,.fd-ibg-demo-page:focus-visible{outline:2px solid rgba(10,132,255,.7);outline-offset:2px}338.fd-ibg-demo-main{max-width:760px;margin:0 auto;padding:40px clamp(16px,4vw,32px) 64px;display:flex;flex-direction:column;align-items:center;gap:20px}339.fd-ibg-demo-toolbar{padding-top:28px;max-width:100%}340.fd-ibg-demo-page{width:100%;padding:clamp(24px,5vw,56px);border-radius:20px;background:var(--d-page);box-shadow:0 0 0 1px var(--d-line),0 1px 2px rgba(0,0,0,.04),0 24px 48px -28px rgba(0,0,0,.25);outline:0}341.fd-ibg-demo-kicker{font:500 12px/1 var(--d-font);text-transform:uppercase;letter-spacing:.06em;color:var(--d-accent)}342.fd-ibg-demo-page h1{margin-top:16px;font:600 clamp(24px,4vw,32px)/1.15 var(--d-disp);letter-spacing:-.025em}343.fd-ibg-demo-meta{margin-top:12px;font:400 13px/1.4 var(--d-font);color:var(--d-mut)}344.fd-ibg-demo-body{margin-top:28px;font:400 17px/1.65 var(--d-font);letter-spacing:-.01em}345.fd-ibg-demo-body[data-quote]>p{padding-left:18px;border-left:3px solid var(--d-accent);color:var(--d-mut)}346.fd-ibg-demo-list{margin-top:20px;display:flex;flex-direction:column;gap:6px;padding-left:0}347.fd-ibg-demo-list[data-kind="ul"]{list-style:disc;display:block;padding-left:1.4em}348.fd-ibg-demo-list[data-kind="ol"]{list-style:decimal;display:block;padding-left:1.6em}349.fd-ibg-demo-list li{display:list-item;padding:2px 0}.fd-ibg-demo-list[data-kind="plain"] li{display:block}350.fd-ibg-demo-list li::marker{color:var(--d-accent);font-weight:600}351.fd-ibg-demo-mark{padding:1px 0;transition:background-color var(--fd-dur-2) var(--fd-ease-out),color var(--fd-dur-2) var(--fd-ease-out);background:rgba(10,132,255,.14);border-radius:4px;box-decoration-break:clone;-webkit-box-decoration-break:clone}352.fd-ibg-demo-mark[data-marks~="b"]{font-weight:700}.fd-ibg-demo-mark[data-marks~="i"]{font-style:italic}353.fd-ibg-demo-mark[data-marks~="u"]{text-decoration:underline;text-underline-offset:3px}354.fd-ibg-demo-mark[data-marks~="s"]{text-decoration:line-through}.fd-ibg-demo-mark[data-marks~="u"][data-marks~="s"]{text-decoration:underline line-through}355.fd-ibg-demo-mark[data-marks~="c"]{font-family:var(--d-mono);font-size:.9em;padding:2px 6px;background:var(--d-line)}356.fd-ibg-demo-mark[data-marks~="l"]{color:var(--d-accent);text-decoration:underline;text-underline-offset:3px}357.fd-ibg-demo-hint{font:400 13px/1.4 var(--d-font);color:var(--d-mut)}358@media (max-width:640px){.fd-ibg-demo-doc{display:none}}359`;The complete button system: 5 variants, 3 sizes, icon slots, loading → success morph and a springy press.
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.