Input Field
A floating-label input with helper, error and success states, adornments, counter, clear and password reveal.
A ⌘K command palette with fuzzy matching, sections, recent items and complete keyboard navigation.
Browser support · All evergreen browsers. Backdrop blur falls back to an opaque panel with prefers-reduced-transparency.
npx shadcn@latest add https://freedesignmd.com/r/search-command.json1/**2 * SearchCommand — a ⌘K command palette: fuzzy filtering with highlighted3 * matches, grouped sections, recent items, full keyboard navigation and a4 * footer with key hints.5 *6 * Props: open, onOpenChange, sections[] ({ id, heading, items[] }), recent7 * (item ids shown first when the query is empty), onSelect(item),8 * placeholder, hotkey (default true: ⌘K / Ctrl+K toggles), theme.9 * Item = { id, label, hint?, icon?, keywords?, shortcut? }.10 * Exports: SearchCommand, fuzzyScore.11 *12 * Render it near the root of your app: it is position: fixed and does not use13 * a portal (so it works without a DOM target and in SSR).14 * Browser support: all evergreen browsers.15 * Accessibility: role="dialog" aria-modal, the input is a combobox16 * (aria-expanded, aria-controls, aria-activedescendant) over a role="listbox";17 * focus is trapped, Escape closes and returns focus to the opener, results18 * count is announced politely. Keys: ↑/↓, Home/End, Enter, Esc, ⌘K.19 * Reduced motion: opens with a fade only.20 */21import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";22import type { ReactNode } from "react";23 24export interface CommandItem {25 id: string;26 label: string;27 hint?: string;28 icon?: ReactNode;29 keywords?: string;30 shortcut?: string;31}32export interface CommandSection {33 id: string;34 heading: string;35 items: CommandItem[];36}37 38/** Subsequence fuzzy score with bonuses for word starts and runs. Returns null when it does not match. */39export function fuzzyScore(query: string, text: string): { score: number; idx: number[] } | null {40 const q = query.toLowerCase().replace(/\s+/g, "");41 const t = text.toLowerCase();42 if (!q) return { score: 0, idx: [] };43 // exact substring wins44 const sub = t.indexOf(query.toLowerCase().trim());45 if (sub >= 0) return { score: 100 - sub + query.length, idx: Array.from({ length: query.trim().length }, (_, i) => sub + i) };46 let ti = 0;47 let score = 0;48 let run = 0;49 const idx: number[] = [];50 for (const ch of q) {51 const f = t.indexOf(ch, ti);52 if (f < 0) return null;53 const start = f === 0 || /[\s\-_/.#]/.test(t[f - 1]);54 run = f === ti && idx.length ? run + 1 : 0;55 score += 1 + (start ? 6 : 0) + run * 3 - Math.min(f - ti, 6) * 0.2;56 idx.push(f);57 ti = f + 1;58 }59 return { score, idx };60}61 62function Hl({ text, idx }: { text: string; idx: number[] }) {63 if (!idx.length) return <>{text}</>;64 const set = new Set(idx);65 const out: ReactNode[] = [];66 let buf = "";67 let on = false;68 for (let i = 0; i < text.length; i++) {69 const m = set.has(i);70 if (m !== on && buf) {71 out.push(on ? <mark key={i}>{buf}</mark> : buf);72 buf = "";73 }74 on = m;75 buf += text[i];76 }77 if (buf) out.push(on ? <mark key="end">{buf}</mark> : buf);78 return <>{out}</>;79}80 81export interface SearchCommandProps {82 open: boolean;83 onOpenChange: (o: boolean) => void;84 sections: CommandSection[];85 recent?: string[];86 onSelect?: (item: CommandItem, section: CommandSection) => void;87 placeholder?: string;88 hotkey?: boolean;89 theme?: "light" | "dark";90 className?: string;91}92 93export function SearchCommand({ open, onOpenChange, sections, recent = [], onSelect, placeholder = "Search or run a command…", hotkey = true, theme, className }: SearchCommandProps) {94 const uid = useId();95 const [q, setQ] = useState("");96 const [cur, setCur] = useState(0);97 const input = useRef<HTMLInputElement>(null);98 const list = useRef<HTMLDivElement>(null);99 const opener = useRef<Element | null>(null);100 const [mouse, setMouse] = useState(false);101 102 useEffect(() => {103 if (!hotkey) return;104 const on = (e: KeyboardEvent) => {105 if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {106 e.preventDefault();107 onOpenChange(!open);108 }109 };110 window.addEventListener("keydown", on);111 return () => window.removeEventListener("keydown", on);112 }, [hotkey, open, onOpenChange]);113 114 useEffect(() => {115 if (open) {116 opener.current = document.activeElement;117 setQ("");118 setCur(0);119 const t = window.setTimeout(() => input.current?.focus(), 20);120 return () => window.clearTimeout(t);121 }122 (opener.current as HTMLElement | null)?.focus?.();123 }, [open]);124 125 const groups = useMemo(() => {126 const all = sections.flatMap((s) => s.items.map((i) => ({ i, s })));127 if (!q.trim()) {128 const rec = recent.map((id) => all.find((x) => x.i.id === id)).filter(Boolean) as typeof all;129 const out: { section: CommandSection; heading: string; rows: { item: CommandItem; idx: number[] }[] }[] = [];130 if (rec.length) out.push({ section: rec[0].s, heading: "Recent", rows: rec.map((x) => ({ item: x.i, idx: [] })) });131 sections.forEach((s) => s.items.some((i) => !recent.includes(i.id)) && out.push({ section: s, heading: s.heading, rows: s.items.filter((i) => !recent.includes(i.id)).slice(0, 4).map((i) => ({ item: i, idx: [] })) }));132 return out;133 }134 return sections135 .map((s) => {136 const rows = s.items137 .map((item) => {138 const a = fuzzyScore(q, item.label);139 const b = a ? null : fuzzyScore(q, `${item.keywords ?? ""} ${item.hint ?? ""}`);140 if (!a && !b) return null;141 return { item, idx: a?.idx ?? [], score: a ? a.score + 20 : (b?.score ?? 0) };142 })143 .filter(Boolean) as { item: CommandItem; idx: number[]; score: number }[];144 rows.sort((x, y) => y.score - x.score);145 return { section: s, heading: s.heading, rows, top: rows[0]?.score ?? -1 };146 })147 .filter((g) => g.rows.length)148 .sort((a, b) => (b as { top: number }).top - (a as { top: number }).top);149 }, [q, sections, recent]);150 151 const flat = groups.flatMap((g) => g.rows.map((r) => ({ ...r, section: g.section })));152 useEffect(() => setCur(0), [q]);153 useEffect(() => {154 list.current?.querySelector<HTMLElement>(`[data-i="${cur}"]`)?.scrollIntoView({ block: "nearest" });155 }, [cur]);156 157 const run = useCallback(158 (n: number) => {159 const r = flat[n];160 if (!r) return;161 onSelect?.(r.item, r.section);162 onOpenChange(false);163 },164 [flat, onSelect, onOpenChange],165 );166 167 const onKey = (e: React.KeyboardEvent) => {168 setMouse(false);169 if (e.key === "ArrowDown") (e.preventDefault(), setCur((c) => (c + 1) % Math.max(flat.length, 1)));170 else if (e.key === "ArrowUp") (e.preventDefault(), setCur((c) => (c - 1 + flat.length) % Math.max(flat.length, 1)));171 else if (e.key === "Home") (e.preventDefault(), setCur(0));172 else if (e.key === "End") (e.preventDefault(), setCur(flat.length - 1));173 else if (e.key === "Enter") (e.preventDefault(), run(cur));174 else if (e.key === "Escape") (e.preventDefault(), onOpenChange(false));175 else if (e.key === "Tab") e.preventDefault(); // keep focus in the palette176 };177 178 if (!open) return <style>{css}</style>;179 180 let n = -1;181 return (182 <div className={`fd-cmd${className ? " " + className : ""}`} data-fd-theme={theme}>183 <style>{css}</style>184 <div className="fd-cmd-scrim" onMouseDown={() => onOpenChange(false)} />185 <div className="fd-cmd-panel" role="dialog" aria-modal="true" aria-label="Command palette" onKeyDown={onKey}>186 <div className="fd-cmd-top">187 <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7" /><path d="M20 20l-3.5-3.5" /></svg>188 <input189 ref={input}190 role="combobox"191 aria-expanded="true"192 aria-controls={`${uid}-list`}193 aria-activedescendant={flat.length ? `${uid}-o${cur}` : undefined}194 aria-autocomplete="list"195 autoComplete="off"196 spellCheck={false}197 placeholder={placeholder}198 value={q}199 onChange={(e) => setQ(e.target.value)}200 />201 {q && <button type="button" className="fd-cmd-clear" aria-label="Clear search" onClick={() => { setQ(""); input.current?.focus(); }}>Clear</button>}202 <kbd>esc</kbd>203 </div>204 205 <div ref={list} id={`${uid}-list`} className="fd-cmd-list" role="listbox" aria-label="Results" onMouseMove={() => !mouse && setMouse(true)}>206 {flat.length === 0 ? (207 <div className="fd-cmd-empty">208 <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7" /><path d="M20 20l-3.5-3.5M8.5 11h5" /></svg>209 <p>No results for “{q}”</p>210 <span>Try a shipment ID, warehouse code or action.</span>211 </div>212 ) : (213 groups.map((g) => (214 <div key={g.heading + g.section.id} role="group" aria-label={g.heading} className="fd-cmd-group">215 <div className="fd-cmd-head" aria-hidden="true">{g.heading}</div>216 {g.rows.map((r) => {217 n++;218 const i = n;219 return (220 <div221 key={r.item.id + g.heading}222 id={`${uid}-o${i}`}223 role="option"224 aria-selected={i === cur}225 data-i={i}226 className="fd-cmd-item"227 data-active={i === cur || undefined}228 onMouseMove={() => mouse && setCur(i)}229 onClick={() => run(i)}230 >231 <span className="fd-cmd-ic">{r.item.icon}</span>232 <span className="fd-cmd-txt">233 <span className="fd-cmd-label"><Hl text={r.item.label} idx={r.idx} /></span>234 {r.item.hint && <span className="fd-cmd-hint">{r.item.hint}</span>}235 </span>236 {r.item.shortcut && <kbd>{r.item.shortcut}</kbd>}237 <svg className="fd-cmd-enter" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M20 5v7a3 3 0 0 1-3 3H5M9 11l-4 4 4 4" /></svg>238 </div>239 );240 })}241 </div>242 ))243 )}244 </div>245 246 <div className="fd-cmd-foot" aria-hidden="true">247 <span><kbd>↑</kbd><kbd>↓</kbd> navigate</span>248 <span><kbd>↵</kbd> select</span>249 <span><kbd>esc</kbd> close</span>250 <b>{flat.length} {flat.length === 1 ? "result" : "results"}</b>251 </div>252 <span className="fd-cmd-sr" role="status" aria-live="polite">{flat.length} results</span>253 </div>254 </div>255 );256}257 258const css = `259:where(.fd-cmd,.fd-cmd *){box-sizing:border-box;margin:0;padding:0;font:inherit;letter-spacing:inherit;color:inherit;text-decoration:none;list-style:none}260:where(.fd-cmd,.fd-cmd *)::before,:where(.fd-cmd,.fd-cmd *)::after{box-sizing:border-box}261:where(.fd-cmd svg){display:block;flex-shrink:0}262:where(.fd-cmd button,.fd-cmd input){appearance:none;-webkit-appearance:none;background:none;border:0;border-radius:0;outline:0}263.fd-cmd{264 --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;265 --c-bg:rgba(255,255,255,.92);--c-solid:#fff;--c-fg:#0B0B0F;--c-mut:#6B6B76;--c-line:rgba(11,11,15,.08);--c-hover:rgba(11,11,15,.05);--c-active:rgba(0,122,255,.1);--c-accent:#007AFF;--c-mark:#0058C7;--c-kbd:rgba(11,11,15,.06);266 position:fixed;inset:0;z-index:1000;display:flex;align-items:flex-start;justify-content:center;padding:min(14vh,120px) 16px 16px;267 font:400 15px/1.4 ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif;letter-spacing:-.005em;color:var(--c-fg);268}269[data-fd-theme="dark"].fd-cmd,.fd-cmd[data-fd-theme="dark"]{--c-bg:rgba(24,24,30,.86);--c-solid:#18181E;--c-fg:#F5F5F7;--c-mut:#9A9AA6;--c-line:rgba(255,255,255,.09);--c-hover:rgba(255,255,255,.06);--c-active:rgba(10,132,255,.22);--c-accent:#0A84FF;--c-mark:#6CB4FF;--c-kbd:rgba(255,255,255,.1)}270.fd-cmd-scrim{position:absolute;inset:0;background:rgba(11,11,15,.35);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);animation:fd-cmd-fade var(--fd-dur-2) var(--fd-ease-out)}271.fd-cmd-panel{position:relative;display:flex;flex-direction:column;width:100%;max-width:640px;max-height:min(560px,calc(100vh - 140px));border-radius:20px;overflow:hidden;272 background:var(--c-bg);-webkit-backdrop-filter:blur(28px) saturate(1.6);backdrop-filter:blur(28px) saturate(1.6);273 box-shadow:0 0 0 1px var(--c-line),0 1px 0 rgba(255,255,255,.1) inset,0 4px 8px rgba(0,0,0,.06),0 40px 80px -20px rgba(0,0,0,.45);animation:fd-cmd-in var(--fd-dur-3) var(--fd-spring)}274.fd-cmd-top{flex:none;display:flex;align-items:center;gap:12px;padding:0 16px;height:60px;border-bottom:1px solid var(--c-line);color:var(--c-mut)}275.fd-cmd-top input{flex:1;min-width:0;height:100%;color:var(--c-fg);font-family:inherit;font-size:17px;letter-spacing:-.01em;caret-color:var(--c-accent)}276.fd-cmd-top input::placeholder{color:var(--c-mut);opacity:.8}277.fd-cmd-clear{cursor:pointer;height:24px;padding:0 8px;border-radius:6px;font-family:inherit;font-size:12px;font-weight:500;color:var(--c-mut)}.fd-cmd-clear:hover{background:var(--c-hover);color:var(--c-fg)}278.fd-cmd-clear:focus-visible{outline:2px solid var(--c-accent);outline-offset:1px}279.fd-cmd kbd{display:inline-grid;place-items:center;min-width:22px;height:22px;padding:0 6px;border-radius:6px;background:var(--c-kbd);color:var(--c-mut);font:500 11px/1 ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace}280.fd-cmd-list{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:8px;scroll-padding:8px}281.fd-cmd-group+.fd-cmd-group{margin-top:6px}282.fd-cmd-head{padding:10px 12px 6px;font-size:12px;font-weight:600;line-height:1;letter-spacing:.02em;color:var(--c-mut)}283.fd-cmd-item{display:flex;align-items:center;gap:12px;min-height:48px;padding:8px 12px;border-radius:12px;cursor:pointer;transition:background-color var(--fd-dur-1) var(--fd-ease-out)}284.fd-cmd-item[data-active]{background:var(--c-active)}285.fd-cmd-ic{display:grid;place-items:center;width:32px;height:32px;border-radius:9px;background:var(--c-hover);color:var(--c-mut);transition:background-color var(--fd-dur-1),color var(--fd-dur-1)}286.fd-cmd-item[data-active] .fd-cmd-ic{background:var(--c-accent);color:#fff}287.fd-cmd-ic svg{width:16px;height:16px}288.fd-cmd-txt{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}289.fd-cmd-label{font-size:15px;font-weight:500;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}290.fd-cmd-hint{font-size:12.5px;line-height:1.3;color:var(--c-mut);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}291.fd-cmd mark{background:none;color:var(--c-mark);font-weight:700}292.fd-cmd-enter{opacity:0;color:var(--c-mut);transform:translateX(-4px);transition:opacity var(--fd-dur-1),transform var(--fd-dur-2) var(--fd-ease-out)}293.fd-cmd-item[data-active] .fd-cmd-enter{opacity:1;transform:none}294.fd-cmd-item[data-active] kbd{display:none}295.fd-cmd-empty{display:flex;flex-direction:column;align-items:center;gap:6px;padding:48px 16px;text-align:center;color:var(--c-mut)}296.fd-cmd-empty p{margin-top:8px;font-size:15px;font-weight:500;color:var(--c-fg)}.fd-cmd-empty span{font-size:13px}297.fd-cmd-foot{flex:none;display:flex;align-items:center;gap:16px;padding:0 16px;height:44px;border-top:1px solid var(--c-line);font-size:12px;color:var(--c-mut)}298.fd-cmd-foot span{display:inline-flex;align-items:center;gap:4px}.fd-cmd-foot kbd{min-width:20px;height:20px}299.fd-cmd-foot b{margin-left:auto;font-weight:500;font-variant-numeric:tabular-nums}300.fd-cmd-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}301@keyframes fd-cmd-fade{from{opacity:0}}302@keyframes fd-cmd-in{from{opacity:0;transform:translateY(-12px) scale(.96)}}303@media (max-width:560px){.fd-cmd{padding-top:12px}.fd-cmd-foot span:nth-child(n+2){display:none}.fd-cmd-panel{max-height:calc(100vh - 28px)}}304@media (prefers-reduced-motion:reduce){.fd-cmd-panel{animation:fd-cmd-fade 100ms linear}.fd-cmd-scrim{animation:none}.fd-cmd-enter{transition:none}}305@media (prefers-reduced-transparency:reduce),(prefers-contrast:more){.fd-cmd-panel{background:var(--c-solid);-webkit-backdrop-filter:none;backdrop-filter:none;box-shadow:0 0 0 1px currentColor}.fd-cmd-scrim{-webkit-backdrop-filter:none;backdrop-filter:none}}306`;307 308/* ───────────────────────────── demo ───────────────────────────── */309 310const I = (d: string) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d={d} /></svg>;311const box = I("M21 8l-9-5-9 5v8l9 5 9-5zM3 8l9 5 9-5M12 13v8");312const truck = I("M1 6h13v10H1zM14 10h5l3 3v3h-8M6 19a2 2 0 1 0 0-4 2 2 0 0 0 0 4zM18 19a2 2 0 1 0 0-4 2 2 0 0 0 0 4z");313const pin = I("M12 21s7-6 7-12a7 7 0 1 0-14 0c0 6 7 12 7 12zM12 11.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z");314const plus = I("M12 5v14M5 12h14");315const alert = I("M12 3l10 18H2zM12 10v5M12 18h.01");316const grid = I("M3 3h7v7H3zM14 3h7v7h-7zM3 14h7v7H3zM14 14h7v7h-7z");317const user = I("M20 21a8 8 0 0 0-16 0M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8z");318const moon = I("M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5z");319 320const SECTIONS: CommandSection[] = [321 {322 id: "ship",323 heading: "Shipments",324 items: [325 { id: "s1", label: "SHP-48213 · Rotterdam → Chicago", hint: "In transit · ETA 3 Nov · 12 pallets", icon: box, keywords: "container ocean maersk" },326 { id: "s2", label: "SHP-48190 · Shenzhen → Los Angeles", hint: "Customs hold · Port of LA", icon: alert, keywords: "delay hold customs" },327 { id: "s3", label: "SHP-48177 · Hamburg → Singapore", hint: "Delivered 27 Oct · POD signed", icon: box, keywords: "delivered" },328 { id: "s4", label: "SHP-48251 · Monterrey → Dallas", hint: "Out for delivery · Driver R. Alvarez", icon: truck, keywords: "truck road ftl" },329 ],330 },331 {332 id: "wh",333 heading: "Warehouses",334 items: [335 { id: "w1", label: "DC-04 Memphis Hub", hint: "92% capacity · 38 docks", icon: pin, keywords: "tennessee distribution center" },336 { id: "w2", label: "DC-11 Rotterdam Maasvlakte", hint: "71% capacity · 24 docks", icon: pin, keywords: "netherlands" },337 { id: "w3", label: "DC-07 Reno Crossdock", hint: "58% capacity · 16 docks", icon: pin, keywords: "nevada" },338 ],339 },340 {341 id: "act",342 heading: "Actions",343 items: [344 { id: "a1", label: "Create shipment", hint: "Start a new booking", icon: plus, shortcut: "C" },345 { id: "a2", label: "Reassign carrier", hint: "Move selected loads to another carrier", icon: truck, shortcut: "R" },346 { id: "a3", label: "Escalate to customs broker", hint: "Open a case for held shipments", icon: alert },347 { id: "a4", label: "Toggle dark mode", hint: "Appearance", icon: moon, shortcut: "⌘ D" },348 ],349 },350 {351 id: "nav",352 heading: "Go to",353 items: [354 { id: "n1", label: "Dashboard", icon: grid, shortcut: "G D" },355 { id: "n2", label: "Carriers", icon: truck, shortcut: "G C" },356 { id: "n3", label: "Team settings", icon: user, shortcut: "G S" },357 ],358 },359];360 361const ROWS = [362 ["SHP-48213", "Rotterdam → Chicago", "In transit", "3 Nov", "ok"],363 ["SHP-48190", "Shenzhen → Los Angeles", "Customs hold", "—", "warn"],364 ["SHP-48251", "Monterrey → Dallas", "Out for delivery", "Today", "ok"],365 ["SHP-48177", "Hamburg → Singapore", "Delivered", "27 Oct", "done"],366 ["SHP-48302", "Antwerp → Newark", "Booked", "9 Nov", "idle"],367];368 369export function Demo() {370 const [theme, setTheme] = useState<"light" | "dark">("light");371 const [open, setOpen] = useState(false);372 const [recent, setRecent] = useState<string[]>(["s2", "a1", "w1"]);373 const [log, setLog] = useState("");374 const isMac = useMemo(() => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform), []);375 const [mac, setMac] = useState(true);376 useEffect(() => setMac(isMac), [isMac]);377 378 return (379 <div className="fd-cmd-demo" data-fd-theme={theme}>380 <style>{demoCss}</style>381 <aside className="fd-cmd-demo-side" aria-label="Navigation">382 <div className="fd-cmd-demo-logo"><i aria-hidden="true" />Haulr Ops</div>383 {["Dashboard", "Shipments", "Warehouses", "Carriers", "Reports"].map((n, i) => <a key={n} href={`#${n}`} aria-current={i === 0 ? "page" : undefined}>{n}</a>)}384 </aside>385 <main className="fd-cmd-demo-main">386 <div className="fd-cmd-demo-top">387 <button type="button" className="fd-cmd-demo-search" onClick={() => setOpen(true)} aria-haspopup="dialog">388 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7" /><path d="M20 20l-3.5-3.5" /></svg>389 <span>Search shipments, sites, actions…</span>390 <kbd>{mac ? "⌘" : "Ctrl"} K</kbd>391 </button>392 <button type="button" className="fd-cmd-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>393 </div>394 <h1>Operations overview</h1>395 <section className="fd-cmd-demo-kpis" aria-label="Key metrics">396 {[["On-time delivery", "94.2%", "+1.1 pt"], ["Active shipments", "1,284", "+86"], ["At risk", "17", "−4"], ["Dock utilisation", "81%", "+3 pt"]].map(([k, v, d]) => (397 <div key={k}><small>{k}</small><b>{v}</b><span data-neg={d.startsWith("−") && k !== "At risk" ? "" : undefined}>{d} vs. last week</span></div>398 ))}399 </section>400 <section className="fd-cmd-demo-table" aria-label="Recent shipments">401 <div className="fd-cmd-demo-th"><span>Shipment</span><span>Lane</span><span>Status</span><span>ETA</span></div>402 {ROWS.map(([id, lane, st, eta, k]) => (403 <div key={id} className="fd-cmd-demo-tr"><b>{id}</b><span>{lane}</span><span data-k={k}><i />{st}</span><span>{eta}</span></div>404 ))}405 </section>406 {log && <p className="fd-cmd-demo-log" role="status">Ran: {log}</p>}407 </main>408 <SearchCommand409 open={open}410 onOpenChange={setOpen}411 sections={SECTIONS}412 recent={recent}413 theme={theme}414 onSelect={(it) => {415 setLog(it.label);416 setRecent((r) => [it.id, ...r.filter((x) => x !== it.id)].slice(0, 4));417 if (it.id === "a4") setTheme((t) => (t === "dark" ? "light" : "dark"));418 }}419 />420 </div>421 );422}423 424const demoCss = `425:where(.fd-cmd-demo,.fd-cmd-demo *){box-sizing:border-box;margin:0;padding:0;font:inherit;letter-spacing:inherit;color:inherit;text-decoration:none;list-style:none}426:where(.fd-cmd-demo,.fd-cmd-demo *)::before,:where(.fd-cmd-demo,.fd-cmd-demo *)::after{box-sizing:border-box}427:where(.fd-cmd-demo button){appearance:none;-webkit-appearance:none;background:none;border:0;border-radius:0}428.fd-cmd-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;429 --fd-ease-out:cubic-bezier(.22,1,.36,1);--fd-dur-2:240ms;430 --d-bg:#F5F6F8;--d-card:#fff;--d-ink:#0B0B0F;--d-mut:#6B6B76;--d-line:rgba(11,11,15,.08);--d-accent:#007AFF;--d-side:#fff;431 display:grid;grid-template-columns:232px minmax(0,1fr);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)}432.fd-cmd-demo[data-fd-theme="dark"]{--d-bg:#08090B;--d-card:#101014;--d-ink:#F5F5F7;--d-mut:#9A9AA6;--d-line:rgba(255,255,255,.08);--d-accent:#0A84FF;--d-side:#0B0B0F}433.fd-cmd-demo-side{display:flex;flex-direction:column;gap:2px;padding:20px 12px;background:var(--d-side);border-right:1px solid var(--d-line)}434.fd-cmd-demo-logo{display:flex;align-items:center;gap:10px;margin:0 8px 20px;font:700 16px/1 var(--d-disp);letter-spacing:-.02em}435.fd-cmd-demo-logo i{width:20px;height:20px;border-radius:6px;background:#F97316}436.fd-cmd-demo-side a{display:flex;align-items:center;height:34px;padding:0 10px;border-radius:8px;font:400 14px/1 var(--d-font);color:var(--d-mut)}437.fd-cmd-demo-side a:hover,.fd-cmd-demo-side a[aria-current]{background:var(--d-line);color:var(--d-ink)}.fd-cmd-demo-side a[aria-current]{font-weight:500}438.fd-cmd-demo-main{display:flex;flex-direction:column;gap:20px;padding:20px clamp(16px,3vw,40px) 48px;min-width:0}439.fd-cmd-demo-top{display:flex;gap:12px;align-items:center}440.fd-cmd-demo-search{display:flex;align-items:center;gap:10px;flex:1;max-width:460px;height:40px;padding:0 8px 0 14px;border-radius:12px;cursor:pointer;text-align:left;background:var(--d-card);color:var(--d-mut);box-shadow:0 0 0 1px var(--d-line),0 1px 2px rgba(0,0,0,.04);font:400 14px/1 var(--d-font);transition:box-shadow var(--fd-dur-2) var(--fd-ease-out)}441.fd-cmd-demo-search:hover{box-shadow:0 0 0 1px var(--d-mut),0 1px 2px rgba(0,0,0,.04)}.fd-cmd-demo-search span{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}442.fd-cmd-demo-search kbd{display:grid;place-items:center;height:24px;padding:0 8px;border-radius:6px;background:var(--d-line);font:500 11px/1 ui-monospace,"SF Mono",Menlo,monospace}443.fd-cmd-demo-search:focus-visible,.fd-cmd-demo-theme:focus-visible{outline:2px solid rgba(10,132,255,.7);outline-offset:2px}444.fd-cmd-demo-theme{cursor:pointer;margin-left:auto;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)}.fd-cmd-demo-theme:hover{color:var(--d-ink)}445.fd-cmd-demo-main h1{font:600 24px/1.15 var(--d-disp);letter-spacing:-.02em}446.fd-cmd-demo-kpis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}447.fd-cmd-demo-kpis div{display:flex;flex-direction:column;gap:8px;padding:16px 18px;border-radius:16px;background:var(--d-card);box-shadow:0 0 0 1px var(--d-line)}448.fd-cmd-demo-kpis small{font:400 13px/1 var(--d-font);color:var(--d-mut)}.fd-cmd-demo-kpis b{font:600 26px/1 var(--d-disp);letter-spacing:-.03em;font-variant-numeric:tabular-nums}449.fd-cmd-demo-kpis span{font:500 12px/1 var(--d-font);color:#1C9A44}.fd-cmd-demo-kpis span[data-neg]{color:#E5484D}450.fd-cmd-demo-table{border-radius:16px;background:var(--d-card);box-shadow:0 0 0 1px var(--d-line);overflow:hidden}451.fd-cmd-demo-th,.fd-cmd-demo-tr{display:grid;grid-template-columns:110px minmax(0,1.6fr) minmax(0,1fr) 80px;gap:16px;align-items:center;padding:0 20px;height:48px;font:400 14px/1 var(--d-font)}452.fd-cmd-demo-th{height:40px;font:500 12px/1 var(--d-font);color:var(--d-mut);border-bottom:1px solid var(--d-line)}453.fd-cmd-demo-tr+.fd-cmd-demo-tr{border-top:1px solid var(--d-line)}.fd-cmd-demo-tr b{font:500 13px/1 ui-monospace,"SF Mono",Menlo,monospace}454.fd-cmd-demo-tr span:nth-child(3){display:inline-flex;align-items:center;gap:8px}.fd-cmd-demo-tr i{width:6px;height:6px;border-radius:50%;background:var(--d-mut)}455.fd-cmd-demo-tr [data-k="ok"] i{background:#30D158}.fd-cmd-demo-tr [data-k="warn"] i{background:#FF9F0A}.fd-cmd-demo-tr [data-k="done"] i{background:var(--d-accent)}456.fd-cmd-demo-log{font:500 13px/1.3 var(--d-font);color:var(--d-accent)}457@media (max-width:900px){.fd-cmd-demo{grid-template-columns:1fr}.fd-cmd-demo-side{display:none}.fd-cmd-demo-kpis{grid-template-columns:repeat(2,minmax(0,1fr))}.fd-cmd-demo-th,.fd-cmd-demo-tr{grid-template-columns:96px minmax(0,1fr) 72px}.fd-cmd-demo-th span:nth-child(2),.fd-cmd-demo-tr span:nth-child(2){display:none}}458`;A floating-label input with helper, error and success states, adornments, counter, clear and password reveal.
Six-digit code entry with auto-advance, paste and SMS autofill, error shake and a success cascade.
Card entry with brand detection, live formatting, Luhn validation and a preview that flips to the CVC.