Liquid Glass
The material itself: a lens that refracts what’s behind it, with squircle bevel, specular rim, dispersion and spring physics.
A glass button that morphs — one surface, springing open — into its menu and back, with full keyboard support.
Browser support · Real refraction (SVG displacement inside backdrop-filter) in Chromium browsers. Safari and Firefox fall back to a frosted blur with the same rim and highlights.
npx shadcn@latest add https://freedesignmd.com/r/glass-menu.json1/**2 * GlassMenu — a Liquid Glass button that morphs into its menu and back.3 *4 * One glass surface, not two: the capsule's width, height and corner radius5 * spring from button-size to menu-size (anchored to the button's corner), the6 * button face cross-fades out and the menu items cascade in. The refraction7 * map follows the shape every frame. Regular variant — menus carry text.8 *9 * Props: label (button text, or aria-label for icon buttons), icon,10 * items: ({ id, label, icon?, detail?, destructive?, disabled? } | "separator")[],11 * onSelect(id), align ("start" | "end" — which corner stays anchored),12 * placement ("down" | "up"), tone ("light" | "dark"), menuWidth (px).13 * Keyboard: Enter/Space/ArrowDown open (focus first item), ArrowUp opens on14 * last; Arrow keys/Home/End move; Enter selects; Escape or Tab closes and15 * returns focus. Click outside closes. role="menu"/"menuitem",16 * aria-haspopup, aria-expanded, aria-controls.17 * Reduced motion: shape changes instantly; content fades only.18 * Browser support: refraction in Chromium; frosted glass elsewhere.19 */20import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from "react";21import type { CSSProperties, KeyboardEvent, ReactNode } from "react";22import { GlassScope, LiquidGlass, useGlassEnv, useSpring } from "./liquid-glass";23 24export type GlassMenuItem = { id: string; label: string; icon?: ReactNode; detail?: string; destructive?: boolean; disabled?: boolean } | "separator";25 26export interface GlassMenuProps {27 label: string;28 icon?: ReactNode;29 /** Show only the icon on the button (label becomes aria-label). */30 iconOnly?: boolean;31 items: GlassMenuItem[];32 onSelect?: (id: string) => void;33 align?: "start" | "end";34 placement?: "down" | "up";35 tone?: "light" | "dark";36 menuWidth?: number;37 /** Button height in px (default 44). */38 size?: number;39 className?: string;40}41 42const useIso = typeof window === "undefined" ? useEffect : useLayoutEffect;43const ITEM_H = 44;44const SEP_H = 9;45const PAD = 8;46const MENU_R = 26;47 48export function GlassMenu({49 label,50 icon,51 iconOnly = false,52 items,53 onSelect,54 align = "start",55 placement = "down",56 tone = "light",57 menuWidth = 260,58 size = 44,59 className,60}: GlassMenuProps) {61 const env = useGlassEnv();62 const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");63 const [open, setOpen] = useState(false);64 const [render, setRender] = useState(false); // keep menu mounted while closing65 const wrap = useRef<HTMLDivElement | null>(null);66 const surf = useRef<HTMLElement | null>(null);67 const btn = useRef<HTMLButtonElement | null>(null);68 const face = useRef<HTMLSpanElement | null>(null);69 const menu = useRef<HTMLDivElement | null>(null);70 const focusOnOpen = useRef<"first" | "last" | null>(null);71 const closed = useRef({ w: size, h: size });72 73 const menuH = items.reduce((h, it) => h + (it === "separator" ? SEP_H : ITEM_H), 0) + PAD * 2;74 75 // progress spring 0 (button) → 1 (menu); geometry is interpolated from it76 const paint = (p: number) => {77 const s = surf.current;78 if (!s) return;79 const c = closed.current;80 const q = Math.max(0, p);81 const w = c.w + (menuWidth - c.w) * q;82 const h = c.h + (menuH - c.h) * q;83 const r = Math.min(h / 2, c.h / 2 + (MENU_R - c.h / 2) * Math.min(1, q));84 s.style.width = `${w.toFixed(2)}px`;85 s.style.height = `${h.toFixed(2)}px`;86 s.style.borderRadius = `${r.toFixed(2)}px`;87 s.style.setProperty("--fd-gmn-p", Math.min(1, Math.max(0, p)).toFixed(3));88 if (face.current) face.current.style.opacity = String(Math.max(0, 1 - q * 3.2).toFixed(3));89 if (menu.current) menu.current.style.opacity = String(Math.min(1, Math.max(0, (q - 0.35) / 0.5)).toFixed(3));90 };91 const prog = useSpring(0, paint, { stiffness: 340, damping: 27, precision: 0.002 });92 93 // measure the resting button94 useIso(() => {95 const b = btn.current;96 if (!b) return;97 closed.current = { w: b.offsetWidth, h: b.offsetHeight };98 if (!open && prog.value === 0) paint(0);99 // eslint-disable-next-line react-hooks/exhaustive-deps100 }, [label, icon, iconOnly, size]);101 102 const show = useCallback(103 (where: "first" | "last" | null) => {104 focusOnOpen.current = where;105 setRender(true);106 setOpen(true);107 },108 [],109 );110 const hide = useCallback((returnFocus = true) => {111 setOpen(false);112 if (returnFocus) requestAnimationFrame(() => btn.current?.focus({ preventScroll: true }));113 }, []);114 115 useEffect(() => {116 if (open) {117 prog.stiffness = 340;118 prog.damping = 27;119 if (env.reducedMotion) prog.set(1);120 else prog.to(1);121 const where = focusOnOpen.current;122 if (where) {123 requestAnimationFrame(() => {124 const list = menu.current?.querySelectorAll<HTMLElement>("[role=menuitem]:not([aria-disabled=true])");125 if (!list?.length) return;126 (where === "first" ? list[0] : list[list.length - 1]).focus({ preventScroll: true });127 });128 } else requestAnimationFrame(() => menu.current?.focus({ preventScroll: true }));129 } else if (render) {130 prog.stiffness = 420;131 prog.damping = 32;132 if (env.reducedMotion) prog.set(0);133 else prog.to(0);134 const t = window.setTimeout(() => setRender(false), env.reducedMotion ? 0 : 420);135 return () => window.clearTimeout(t);136 }137 // eslint-disable-next-line react-hooks/exhaustive-deps138 }, [open]);139 140 // outside click closes141 useEffect(() => {142 if (!open) return;143 const onDown = (e: PointerEvent) => {144 if (!wrap.current?.contains(e.target as Node)) hide(false);145 };146 document.addEventListener("pointerdown", onDown, true);147 return () => document.removeEventListener("pointerdown", onDown, true);148 }, [open, hide]);149 150 const onBtnKey = (e: KeyboardEvent<HTMLButtonElement>) => {151 if (e.key === "ArrowDown") {152 e.preventDefault();153 show("first");154 } else if (e.key === "ArrowUp") {155 e.preventDefault();156 show("last");157 }158 };159 const onMenuKey = (e: KeyboardEvent<HTMLDivElement>) => {160 const list = Array.from(menu.current?.querySelectorAll<HTMLElement>("[role=menuitem]:not([aria-disabled=true])") ?? []);161 const i = list.indexOf(document.activeElement as HTMLElement);162 if (e.key === "Escape") {163 e.preventDefault();164 hide();165 } else if (e.key === "Tab") {166 hide(false);167 } else if (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Home" || e.key === "End") {168 e.preventDefault();169 const n = e.key === "Home" ? 0 : e.key === "End" ? list.length - 1 : (i + (e.key === "ArrowDown" ? 1 : -1) + list.length) % list.length;170 list[n]?.focus();171 }172 };173 174 const choose = (id: string) => {175 onSelect?.(id);176 hide();177 };178 179 return (180 <GlassScope>181 <div ref={wrap} className={["fd-gmn", `fd-gmn--${align}`, `fd-gmn--${placement}`, `fd-gmn--${tone}`, open ? "is-open" : "", className ?? ""].filter(Boolean).join(" ")} style={{ "--fd-gmn-h": `${size}px` } as CSSProperties}>182 <style>{css}</style>183 <button184 ref={btn}185 type="button"186 className={`fd-gmn-btn${iconOnly ? " is-icon" : ""}`}187 aria-haspopup="menu"188 aria-expanded={open}189 aria-controls={`fd-gmn-${uid}`}190 aria-label={iconOnly ? label : undefined}191 onClick={() => (open ? hide() : show(null))}192 onKeyDown={onBtnKey}193 >194 {icon ? <span className="fd-gmn-ico" aria-hidden="true">{icon}</span> : null}195 {!iconOnly ? <span className="fd-gmn-lbl">{label}</span> : null}196 {!iconOnly ? (197 <svg className="fd-gmn-chev" viewBox="0 0 12 12" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">198 <path d="m3 4.5 3 3 3-3" />199 </svg>200 ) : null}201 </button>202 <LiquidGlass ref={surf} className="fd-gmn-surf" variant="regular" tone={tone} radius="auto" interactive={!open} aria-hidden={!render || undefined}>203 <span ref={face} className="fd-gmn-face" aria-hidden="true">204 {icon ? <span className="fd-gmn-ico">{icon}</span> : null}205 {!iconOnly ? <span className="fd-gmn-lbl">{label}</span> : null}206 {!iconOnly ? (207 <svg className="fd-gmn-chev" viewBox="0 0 12 12" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">208 <path d="m3 4.5 3 3 3-3" />209 </svg>210 ) : null}211 </span>212 {render ? (213 <div ref={menu} id={`fd-gmn-${uid}`} role="menu" aria-label={label} tabIndex={-1} className="fd-gmn-menu" style={{ width: menuWidth }} onKeyDown={onMenuKey}>214 {items.map((it, i) =>215 it === "separator" ? (216 <div key={`s${i}`} role="separator" className="fd-gmn-sep" />217 ) : (218 <div219 key={it.id}220 role="menuitem"221 tabIndex={-1}222 aria-disabled={it.disabled || undefined}223 className={`fd-gmn-item${it.destructive ? " is-danger" : ""}`}224 style={{ "--i": i } as CSSProperties}225 onClick={() => !it.disabled && choose(it.id)}226 onKeyDown={(e) => {227 if ((e.key === "Enter" || e.key === " ") && !it.disabled) {228 e.preventDefault();229 choose(it.id);230 }231 }}232 >233 <span className="fd-gmn-item-ico" aria-hidden="true">{it.icon}</span>234 <span className="fd-gmn-item-lbl">{it.label}</span>235 {it.detail ? <span className="fd-gmn-item-detail">{it.detail}</span> : null}236 </div>237 ),238 )}239 </div>240 ) : null}241 </LiquidGlass>242 </div>243 </GlassScope>244 );245}246 247const css = `248.fd-gmn,.fd-gmn *,.fd-gmn *::before,.fd-gmn *::after{box-sizing:border-box}249.fd-gmn{position:relative;display:inline-block;vertical-align:middle;margin:0;padding:0;color:#0B0B0F;font:600 15px/1 ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif;letter-spacing:-0.01em}250.fd-gmn--dark{color:#F2F2F7}251.fd-gmn.is-open{z-index:50}252.fd-gmn-btn{position:relative;z-index:2;display:inline-flex;align-items:center;justify-content:center;gap:8px;height:var(--fd-gmn-h);min-width:var(--fd-gmn-h);margin:0;padding:0 16px;border:0;border-radius:999px;background:transparent;color:transparent;253 font:inherit;letter-spacing:inherit;cursor:pointer;-webkit-appearance:none;appearance:none;-webkit-tap-highlight-color:transparent}254.fd-gmn-btn.is-icon{width:var(--fd-gmn-h);padding:0}255.fd-gmn-btn > *{visibility:hidden}256.fd-gmn.is-open .fd-gmn-btn{pointer-events:none}257.fd-gmn-btn:focus-visible{outline:2px solid color-mix(in srgb,#0A84FF 70%,transparent);outline-offset:2px}258.fd-gmn-surf{position:absolute !important;top:0;z-index:1;display:block;overflow:hidden;margin:0;padding:0;pointer-events:none;will-change:width,height}259.fd-gmn.is-open .fd-gmn-surf{pointer-events:auto;z-index:3}260.fd-gmn--start .fd-gmn-surf{left:0}261.fd-gmn--end .fd-gmn-surf{right:0}262.fd-gmn--up .fd-gmn-surf{top:auto;bottom:0}263.fd-gmn-face{position:absolute;top:0;left:0;display:inline-flex;align-items:center;justify-content:center;gap:8px;height:var(--fd-gmn-h);min-width:var(--fd-gmn-h);margin:0;padding:0 16px;white-space:nowrap;color:inherit;font:inherit}264.fd-gmn--end .fd-gmn-face{left:auto;right:0}265.fd-gmn--up .fd-gmn-face{top:auto;bottom:0}266.fd-gmn-btn.is-icon + .fd-gmn-surf .fd-gmn-face{padding:0;width:var(--fd-gmn-h)}267.fd-gmn-ico,.fd-gmn-ico svg{display:block;width:20px;height:20px;margin:0;padding:0}268.fd-gmn-lbl{display:block;margin:0;padding:0}269.fd-gmn-chev{display:block;opacity:0.6;margin:0 -2px 0 -2px}270.fd-gmn-menu{position:absolute;top:0;left:0;margin:0;padding:${PAD}px;outline:none;opacity:0}271.fd-gmn--end .fd-gmn-menu{left:auto;right:0}272.fd-gmn--up .fd-gmn-menu{top:auto;bottom:0}273.fd-gmn-item{display:flex;align-items:center;gap:12px;height:${ITEM_H}px;margin:0;padding:0 14px 0 12px;border-radius:${MENU_R - PAD}px;color:inherit;274 font:500 15px/1 ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif;letter-spacing:-0.01em;text-align:left;cursor:pointer;outline:none;white-space:nowrap;275 transform:translateY(calc((1 - var(--fd-gmn-p,0)) * -6px));transition:background-color var(--fd-dur-1) linear}276.fd-gmn--up .fd-gmn-item{transform:translateY(calc((1 - var(--fd-gmn-p,0)) * 6px))}277.fd-gmn-item:hover,.fd-gmn-item:focus-visible{background:rgba(11,11,15,0.07)}278.fd-gmn--dark .fd-gmn-item:hover,.fd-gmn--dark .fd-gmn-item:focus-visible{background:rgba(255,255,255,0.1)}279.fd-gmn-item:focus-visible{box-shadow:inset 0 0 0 2px color-mix(in srgb,#0A84FF 70%,transparent)}280.fd-gmn-item[aria-disabled="true"]{opacity:0.4;cursor:default;background:none}281.fd-gmn-item.is-danger{color:#E5372C}282.fd-gmn--dark .fd-gmn-item.is-danger{color:#FF6961}283.fd-gmn-item-ico{display:grid;place-items:center;width:22px;height:22px;flex:none;margin:0;padding:0}284.fd-gmn-item-ico svg{display:block;width:20px;height:20px}285.fd-gmn-item-lbl{flex:1;display:block;margin:0;padding:0}286.fd-gmn-item-detail{display:block;margin:0;padding:0;font:500 13px/1 ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace;opacity:0.55}287.fd-gmn-sep{height:1px;margin:4px 12px;padding:0;background:rgba(11,11,15,0.1)}288.fd-gmn--dark .fd-gmn-sep{background:rgba(255,255,255,0.12)}289@media (prefers-reduced-motion: reduce){.fd-gmn-item{transform:none}}290`;291 292/* ───────────────────────── demo: trading position context menu ───────────────────────── */293 294const s = (d: ReactNode) => (295 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round">296 {d}297 </svg>298);299const IC = {300 more: s(<><circle cx="6" cy="12" r="1.4" fill="currentColor" /><circle cx="12" cy="12" r="1.4" fill="currentColor" /><circle cx="18" cy="12" r="1.4" fill="currentColor" /></>),301 buy: s(<path d="M12 19V5M6 11l6-6 6 6" />),302 sell: s(<path d="M12 5v14M6 13l6 6 6-6" />),303 alert: s(<path d="M6 16.5V11a6 6 0 0 1 12 0v5.5l1.5 2h-15ZM10 20.5h4" />),304 stop: s(<><circle cx="12" cy="12" r="8" /><path d="M8.5 12h7" /></>),305 share: s(<path d="M12 4v11M8 7.5 12 4l4 3.5M6 12v6.5a1.5 1.5 0 0 0 1.5 1.5h9a1.5 1.5 0 0 0 1.5-1.5V12" />),306 close: s(<path d="M6 6l12 12M18 6 6 18" />),307 range: s(<><rect x="3.5" y="5" width="17" height="15" rx="3" /><path d="M3.5 10h17M8 3v4M16 3v4" /></>),308 check: s(<path d="m5 12.5 4.5 4.5L19 7.5" />),309};310 311/** Deterministic price series (SSR-safe). */312const SERIES = Array.from({ length: 120 }, (_, i) => {313 const t = i / 119;314 return Math.round((182 + Math.sin(t * 9) * 4 + Math.sin(t * 23 + 1) * 1.6 + t * 14 + Math.cos(t * 47) * 0.7) * 100) / 100;315});316 317export function Demo() {318 const [toast, setToast] = useState<string | null>(null);319 const [range, setRange] = useState("1M");320 useEffect(() => {321 if (!toast) return;322 const t = window.setTimeout(() => setToast(null), 2200);323 return () => window.clearTimeout(t);324 }, [toast]);325 const min = Math.min(...SERIES);326 const max = Math.max(...SERIES);327 const pts = SERIES.map((v, i) => `${((i / (SERIES.length - 1)) * 1000).toFixed(1)},${(260 - ((v - min) / (max - min)) * 220).toFixed(1)}`).join(" ");328 const RANGES = ["1D", "1W", "1M", "3M", "1Y", "All"];329 return (330 <div className="fd-gmnd">331 <style>{demoCss}</style>332 <div className="fd-gmnd-card">333 <header className="fd-gmnd-head">334 <div className="fd-gmnd-tick">335 <span className="fd-gmnd-logo" aria-hidden="true">N</span>336 <div>337 <p className="fd-gmnd-sym">NRDX · Nordlys Energy</p>338 <p className="fd-gmnd-ex">XSTO · Position: 240 shares</p>339 </div>340 </div>341 <div className="fd-gmnd-actions">342 <GlassMenu343 label={range === "All" ? "All time" : range}344 icon={IC.range}345 align="end"346 tone="dark"347 menuWidth={200}348 onSelect={setRange}349 items={RANGES.map((r) => ({ id: r, label: r === "All" ? "All time" : r, icon: r === range ? IC.check : undefined }))}350 />351 <GlassMenu352 label="Position actions"353 iconOnly354 icon={IC.more}355 tone="dark"356 align="end"357 menuWidth={272}358 onSelect={(id) => setToast(359 {360 buy: "Buy order ticket opened",361 sell: "Sell order ticket opened",362 alert: "Alert set at SEK 200.00",363 stop: "Trailing stop set at −4%",364 share: "Link to NRDX copied",365 close: "Close position — confirm in ticket",366 }[id] ?? id,367 )}368 items={[369 { id: "buy", label: "Buy more", icon: IC.buy, detail: "B" },370 { id: "sell", label: "Sell", icon: IC.sell, detail: "S" },371 "separator",372 { id: "alert", label: "Price alert", icon: IC.alert },373 { id: "stop", label: "Trailing stop", icon: IC.stop, detail: "−4%" },374 { id: "share", label: "Share", icon: IC.share },375 "separator",376 { id: "close", label: "Close position", icon: IC.close, destructive: true },377 ]}378 />379 </div>380 </header>381 <div className="fd-gmnd-price">382 <span className="fd-gmnd-big">SEK 198.42</span>383 <span className="fd-gmnd-chg">+12.86 (6.93%)</span>384 </div>385 <p className="fd-gmnd-meta">Market value SEK 47 620.80 · Unrealised +SEK 3 086.40</p>386 <svg className="fd-gmnd-chart" viewBox="0 0 1000 280" preserveAspectRatio="none" aria-label="Price chart, up 6.93% this month" role="img">387 <defs>388 <linearGradient id="fdgmndfill" x1="0" y1="0" x2="0" y2="1">389 <stop offset="0" stopColor="#30D158" stopOpacity="0.35" />390 <stop offset="1" stopColor="#30D158" stopOpacity="0" />391 </linearGradient>392 </defs>393 {[40, 100, 160, 220].map((y) => (394 <line key={y} x1="0" x2="1000" y1={y} y2={y} stroke="rgba(255,255,255,0.07)" strokeWidth="1" vectorEffect="non-scaling-stroke" />395 ))}396 <polygon points={`0,280 ${pts} 1000,280`} fill="url(#fdgmndfill)" />397 <polyline points={pts} fill="none" stroke="#30D158" strokeWidth="2.25" vectorEffect="non-scaling-stroke" strokeLinejoin="round" />398 </svg>399 <dl className="fd-gmnd-stats">400 {[401 ["Open", "196.10"],402 ["Day range", "195.02 – 199.80"],403 ["Volume", "3.21 M"],404 ["P/E", "18.4"],405 ["Mkt cap", "SEK 61.2 bn"],406 ["Div. yield", "2.6%"],407 ].map(([k, v]) => (408 <div key={k}>409 <dt>{k}</dt>410 <dd>{v}</dd>411 </div>412 ))}413 </dl>414 </div>415 <div className={`fd-gmnd-toast${toast ? " is-on" : ""}`} role="status" aria-live="polite">416 {toast}417 </div>418 </div>419 );420}421 422const F = `ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif`;423const D = `"SF Pro Display",ui-sans-serif,-apple-system,BlinkMacSystemFont,"Inter","Segoe UI",Roboto,sans-serif`;424const M = `ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace`;425const demoCss = `426.fd-gmnd,.fd-gmnd *,.fd-gmnd *::before,.fd-gmnd *::after{box-sizing:border-box}427.fd-gmnd{position:relative;min-height:100vh;width:100%;margin:0;padding:48px 20px;display:flex;align-items:center;justify-content:center;overflow:hidden;428 background:radial-gradient(700px 400px at 70% 10%,rgba(48,209,88,0.18),transparent 60%),radial-gradient(600px 500px at 10% 90%,rgba(10,132,255,0.16),transparent 60%),#0B0B0F;color:#F2F2F7;font:400 15px/1.4 ${F};letter-spacing:normal}429.fd-gmnd-card{position:relative;width:100%;max-width:760px;margin:0;padding:24px;border-radius:28px;background:#16161B;box-shadow:inset 0 0 0 0.5px rgba(255,255,255,0.08),0 40px 80px -40px rgba(0,0,0,0.9)}430.fd-gmnd-head{display:flex;align-items:center;justify-content:space-between;gap:16px;margin:0 0 20px;padding:0}431.fd-gmnd-tick{display:flex;align-items:center;gap:12px;min-width:0}432.fd-gmnd-logo{display:grid;place-items:center;flex:none;width:44px;height:44px;border-radius:12px;background:linear-gradient(135deg,#64D2FF,#5E5CE6);font:800 20px/1 ${D};color:#fff}433.fd-gmnd-sym{margin:0;font:700 17px/1.2 ${F};letter-spacing:-0.01em;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}434.fd-gmnd-ex{margin:2px 0 0;font:500 13px/1.2 ${F};color:#8E8E99}435.fd-gmnd-actions{display:flex;gap:10px;align-items:center;flex:none}436.fd-gmnd-price{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin:0 0 4px}437.fd-gmnd-big{font:700 clamp(2.25rem,6vw,3.25rem)/1 ${D};letter-spacing:-0.035em;color:#fff;font-variant-numeric:tabular-nums}438.fd-gmnd-chg{font:600 17px/1 ${F};color:#30D158;font-variant-numeric:tabular-nums}439.fd-gmnd-meta{margin:0 0 16px;font:500 13px/1.4 ${F};color:#8E8E99}440.fd-gmnd-chart{display:block;width:100%;height:220px;margin:0 0 16px}441.fd-gmnd-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:0;margin:0;padding:0;border-top:0.5px solid rgba(255,255,255,0.1)}442.fd-gmnd-stats div{margin:0;padding:12px 0;border-bottom:0.5px solid rgba(255,255,255,0.08)}443.fd-gmnd-stats dt{margin:0 0 2px;font:500 12px/1.2 ${F};color:#8E8E99}444.fd-gmnd-stats dd{margin:0;font:500 15px/1.3 ${M};color:#F2F2F7}445.fd-gmnd-toast{position:absolute;left:50%;bottom:28px;margin:0;padding:12px 18px;border-radius:999px;background:#F2F2F7;color:#0B0B0F;font:600 14px/1 ${F};white-space:nowrap;446 transform:translate(-50%,20px);opacity:0;transition:opacity 240ms cubic-bezier(0.22,1,0.36,1),transform 420ms cubic-bezier(0.34,1.56,0.64,1);pointer-events:none}447.fd-gmnd-toast.is-on{opacity:1;transform:translate(-50%,0)}448@media (max-width:560px){449 .fd-gmnd{padding:20px 12px;align-items:flex-start}450 .fd-gmnd-card{padding:18px}451 .fd-gmnd-ex{display:none}452 .fd-gmnd-sym{font-size:15px}453 .fd-gmnd-tick{gap:10px}454 .fd-gmnd-logo{width:38px;height:38px;font-size:17px}455 .fd-gmnd-actions{gap:8px}456 .fd-gmnd-stats{grid-template-columns:repeat(2,1fr)}457 .fd-gmnd-chart{height:160px}458}459`;The material itself: a lens that refracts what’s behind it, with squircle bevel, specular rim, dispersion and spring physics.
glass, glassProminent, clear and clear-prominent capsules in four sizes, with a springy press and a glow from the touch point.
Floating iOS 26 tab bar: a liquid lens springs between tabs, search sits apart, and it minimizes as you scroll.