Liquid Glass
The material itself: a lens that refracts what’s behind it, with squircle bevel, specular rim, dispersion and spring physics.
Floating iOS 26 tab bar: a liquid lens springs between tabs, search sits apart, and it minimizes as you scroll.
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-tab-bar.json1/**2 * GlassTabBar — iOS 26-style floating tab bar in Liquid Glass.3 *4 * - Floats above content as one regular-glass capsule; Search sits apart at5 * the trailing end in its own circular glass button (HIG).6 * - The selection is a liquid lens: it springs between tabs, stretching along7 * its travel and settling with a small overshoot. Drag across the bar to8 * scrub the lens; it snaps to the nearest tab on release.9 * - `minimizeOnScroll`: pass the scroll container's ref. Scrolling down10 * collapses the bar to the active tab + search; scrolling up expands it.11 *12 * Props: tabs ({ id, label, icon }), value, onChange, onSearch,13 * searchLabel, minimizeOnScroll (RefObject<HTMLElement>), tone, accent,14 * className. Keyboard: Arrow keys / Home / End move between tabs (roving15 * tabindex), Enter/Space activate. role="tablist" + aria-selected.16 * Browser support: refraction in Chromium; frosted fallback elsewhere.17 * Reduced motion: lens jumps without stretch; minimize is a quick fade.18 */19import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";20import type { CSSProperties, KeyboardEvent, ReactNode, RefObject } from "react";21import { GlassScope, LiquidGlass, createVelocityTracker, useGlassEnv, useSpring } from "./liquid-glass";22 23export type GlassTab = { id: string; label: string; icon: ReactNode };24 25export interface GlassTabBarProps {26 tabs: GlassTab[];27 value: string;28 onChange: (id: string) => void;29 onSearch?: () => void;30 searchLabel?: string;31 /** Scroll container to watch; bar minimizes on scroll down, expands on scroll up. */32 minimizeOnScroll?: RefObject<HTMLElement | null>;33 tone?: "light" | "dark";34 /** Colour of the selected tab's icon + label. */35 accent?: string;36 className?: string;37 style?: CSSProperties;38}39 40const useIso = typeof window === "undefined" ? useEffect : useLayoutEffect;41 42export function GlassTabBar({43 tabs,44 value,45 onChange,46 onSearch,47 searchLabel = "Search",48 minimizeOnScroll,49 tone = "light",50 accent,51 className,52 style,53}: GlassTabBarProps) {54 const env = useGlassEnv();55 const list = useRef<HTMLDivElement | null>(null);56 const lens = useRef<HTMLSpanElement | null>(null);57 const [minimized, setMinimized] = useState(false);58 const [dragging, setDragging] = useState(false);59 const active = Math.max(0, tabs.findIndex((t) => t.id === value));60 61 /* lens springs: x + width, with stretch from velocity */62 const paint = () => {63 const n = lens.current;64 if (!n) return;65 const v = env.reducedMotion ? 0 : lx.velocity;66 const stretch = Math.min(0.35, Math.abs(v) * 0.00045);67 n.style.transform = `translateX(${lx.value.toFixed(2)}px) scale(${(1 + stretch + lp.value * 0.1).toFixed(4)}, ${(1 - stretch * 0.55 + lp.value * 0.1).toFixed(4)})`;68 n.style.width = `${lw.value.toFixed(2)}px`;69 };70 const lx = useSpring(0, paint, { stiffness: 380, damping: 28 });71 const lw = useSpring(0, paint, { stiffness: 380, damping: 30 });72 const lp = useSpring(0, paint, { stiffness: 520, damping: 26 }); // press "lift"73 74 const measure = useCallback(75 (i: number) => {76 const l = list.current;77 const btn = l?.querySelectorAll<HTMLElement>("[role=tab]")[i];78 if (!l || !btn) return null;79 return { x: btn.offsetLeft, w: btn.offsetWidth };80 },81 [],82 );83 84 const moveTo = useCallback(85 (i: number, instant = false) => {86 const m = measure(i);87 if (!m) return;88 if (instant || env.reducedMotion) {89 lx.set(m.x);90 lw.set(m.w);91 } else {92 lx.to(m.x);93 lw.to(m.w);94 }95 },96 [measure, env.reducedMotion, lx, lw],97 );98 99 const first = useRef(true);100 useIso(() => {101 moveTo(active, first.current);102 first.current = false;103 }, [active, moveTo]);104 105 // keep the lens glued to its tab while the bar resizes (minimize/expand, viewport)106 useEffect(() => {107 const l = list.current;108 if (!l || typeof ResizeObserver === "undefined") return;109 const ro = new ResizeObserver(() => {110 if (!draggingRef.current) moveTo(activeRef.current, true);111 });112 ro.observe(l);113 return () => ro.disconnect();114 }, [moveTo]);115 const activeRef = useRef(active);116 activeRef.current = active;117 const draggingRef = useRef(false);118 119 /* scrub: drag across the bar, the lens follows the finger and snaps on release */120 useEffect(() => {121 const l = list.current;122 if (!l) return;123 const vt = createVelocityTracker();124 let startX = 0;125 let startLens = 0;126 let moved = false;127 let pid = -1;128 const nearest = (cx: number) => {129 const btns = Array.from(l.querySelectorAll<HTMLElement>("[role=tab]"));130 let best = 0, bd = Infinity;131 btns.forEach((b, i) => {132 const d = Math.abs(b.offsetLeft + b.offsetWidth / 2 - cx);133 if (d < bd) (bd = d), (best = i);134 });135 return best;136 };137 const down = (e: PointerEvent) => {138 if (e.button !== 0 || l.classList.contains("is-min")) return;139 pid = e.pointerId;140 startX = e.clientX;141 startLens = lx.value;142 moved = false;143 vt.reset();144 vt.push(e.clientX, 0);145 // the lens lifts when you touch the selected tab146 const idx = nearest(e.clientX - l.getBoundingClientRect().left);147 if (idx === activeRef.current && !env.reducedMotion) lp.to(1);148 };149 const move = (e: PointerEvent) => {150 if (e.pointerId !== pid) return;151 const dx = e.clientX - startX;152 if (!moved && Math.abs(dx) < 6) return;153 if (!moved) {154 moved = true;155 draggingRef.current = true;156 setDragging(true);157 l.setPointerCapture(pid);158 if (!env.reducedMotion) lp.to(1);159 }160 vt.push(e.clientX, 0);161 const max = l.clientWidth - lw.value;162 let x = startLens + dx;163 if (x < 0) x *= 0.3;164 if (x > max) x = max + (x - max) * 0.3;165 lx.stiffness = 900;166 lx.damping = 50;167 lx.to(x);168 const idx = nearest(x + lw.value / 2);169 const m = measure(idx);170 if (m) lw.to(m.w);171 };172 const up = (e: PointerEvent) => {173 if (e.pointerId !== pid) return;174 pid = -1;175 lp.to(0);176 if (!moved) return;177 draggingRef.current = false;178 setDragging(false);179 lx.stiffness = 380;180 lx.damping = 28;181 const { vx } = vt.get();182 const idx = nearest(lx.value + lw.value / 2 + vx * 0.06);183 const t = tabs[idx];184 if (t && t.id !== value) onChange(t.id);185 else moveTo(idx);186 // swallow the click that follows a drag187 const stop = (ev: Event) => {188 ev.stopPropagation();189 ev.preventDefault();190 };191 l.addEventListener("click", stop, { capture: true, once: true });192 setTimeout(() => l.removeEventListener("click", stop, { capture: true }), 0);193 };194 l.addEventListener("pointerdown", down);195 l.addEventListener("pointermove", move);196 l.addEventListener("pointerup", up);197 l.addEventListener("pointercancel", up);198 return () => {199 l.removeEventListener("pointerdown", down);200 l.removeEventListener("pointermove", move);201 l.removeEventListener("pointerup", up);202 l.removeEventListener("pointercancel", up);203 };204 }, [tabs, value, onChange, measure, moveTo, env.reducedMotion, lx, lw, lp]);205 206 /* minimize on scroll down, expand on scroll up */207 useEffect(() => {208 const sc = minimizeOnScroll?.current;209 if (!sc) return;210 let last = sc.scrollTop;211 let acc = 0;212 let raf = 0;213 const onScroll = () => {214 if (raf) return;215 raf = requestAnimationFrame(() => {216 raf = 0;217 const y = sc.scrollTop;218 const d = y - last;219 last = y;220 if (y < 24) {221 acc = 0;222 setMinimized(false);223 return;224 }225 acc = Math.sign(d) === Math.sign(acc) ? acc + d : d;226 if (acc > 28) setMinimized(true);227 else if (acc < -28) setMinimized(false);228 });229 };230 sc.addEventListener("scroll", onScroll, { passive: true });231 return () => {232 sc.removeEventListener("scroll", onScroll);233 cancelAnimationFrame(raf);234 };235 }, [minimizeOnScroll]);236 237 const onKey = (e: KeyboardEvent<HTMLDivElement>) => {238 const n = tabs.length;239 const map: Record<string, number> = { ArrowRight: active + 1, ArrowLeft: active - 1, Home: 0, End: n - 1 };240 if (!(e.key in map)) return;241 e.preventDefault();242 const i = (map[e.key] + n) % n;243 onChange(tabs[i].id);244 list.current?.querySelectorAll<HTMLElement>("[role=tab]")[i]?.focus();245 };246 247 const cls = ["fd-gtab", minimized ? "is-min" : "", dragging ? "is-drag" : "", `fd-gtab--${tone}`, className ?? ""].filter(Boolean).join(" ");248 249 return (250 <GlassScope>251 <nav className={cls} style={{ ...(accent ? { "--fd-gtab-accent": accent } : null), ...style } as CSSProperties} aria-label="Primary">252 <style>{css}</style>253 <LiquidGlass className="fd-gtab-bar" variant="regular" tone={tone} radius="capsule">254 <div ref={list} className={`fd-gtab-list${minimized ? " is-min" : ""}`} role="tablist" aria-orientation="horizontal" onKeyDown={onKey}>255 <span ref={lens} className="fd-gtab-lens" aria-hidden="true" />256 {tabs.map((t, i) => (257 <button258 key={t.id}259 type="button"260 role="tab"261 id={`fd-gtab-${t.id}`}262 aria-selected={i === active}263 tabIndex={i === active ? 0 : -1}264 className="fd-gtab-tab"265 onClick={() => {266 if (minimized) setMinimized(false);267 else onChange(t.id);268 }}269 >270 <span className="fd-gtab-ico" aria-hidden="true">271 {t.icon}272 </span>273 <span className="fd-gtab-lbl">{t.label}</span>274 </button>275 ))}276 </div>277 </LiquidGlass>278 <LiquidGlass as="button" type="button" className="fd-gtab-search" variant="regular" tone={tone} interactive aria-label={searchLabel} onClick={onSearch}>279 <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" aria-hidden="true">280 <circle cx="10.5" cy="10.5" r="6.5" />281 <path d="m15.5 15.5 5 5" />282 </svg>283 </LiquidGlass>284 </nav>285 </GlassScope>286 );287}288 289const css = `290.fd-gtab,.fd-gtab *,.fd-gtab *::before,.fd-gtab *::after{box-sizing:border-box}291.fd-gtab{--fd-gtab-accent:#007AFF;--fd-gtab-h:62px;--fd-gtab-pad:4px;292 display:flex;align-items:center;gap:10px;margin:0;padding:0;background:none;border:0;293 font:500 10px/1.1 ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif;letter-spacing:0.01em;color:#0B0B0F}294.fd-gtab--dark{--fd-gtab-accent:#0A84FF}295.fd-gtab-bar{flex:1 1 auto;min-width:0;height:var(--fd-gtab-h);padding:var(--fd-gtab-pad);transition:max-width var(--fd-dur-3) var(--fd-ease-out);max-width:600px}296.fd-gtab.is-min .fd-gtab-bar{flex:0 0 auto;max-width:var(--fd-gtab-h)}297.fd-gtab-list{position:relative;display:flex;height:100%;margin:0;padding:0;touch-action:pan-y;user-select:none;-webkit-user-select:none}298.fd-gtab-lens{position:absolute;left:0;top:0;bottom:0;width:0;margin:0;padding:0;border-radius:999px;pointer-events:none;transform-origin:50% 50%;will-change:transform,width;299 background:rgba(11,11,15,0.075);box-shadow:inset 0 0 0 0.5px rgba(255,255,255,0.4)}300.fd-gtab--dark .fd-gtab-lens{background:rgba(255,255,255,0.14)}301.fd-gtab.is-drag .fd-gtab-lens{background:rgba(255,255,255,0.12);box-shadow:inset 0 1px 1px rgba(255,255,255,0.9),inset 0 -1px 1px rgba(255,255,255,0.35),0 6px 18px -4px rgba(0,0,0,0.22),inset 0 0 0 0.5px rgba(255,255,255,0.7);302 -webkit-backdrop-filter:blur(1px) saturate(1.6) brightness(1.08);backdrop-filter:blur(1px) saturate(1.6) brightness(1.08)}303.fd-gtab-tab{position:relative;flex:1 1 0;min-width:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;height:100%;margin:0;padding:0 6px;border:0;border-radius:999px;background:none;color:inherit;304 font:inherit;letter-spacing:inherit;text-shadow:none;cursor:pointer;-webkit-appearance:none;appearance:none;-webkit-tap-highlight-color:transparent;305 transition:opacity var(--fd-dur-2) var(--fd-ease-out),color var(--fd-dur-2) var(--fd-ease-out),transform var(--fd-dur-3) var(--fd-spring)}306.fd-gtab--dark .fd-gtab-tab{color:#F2F2F7}307.fd-gtab-tab[aria-selected="true"]{color:var(--fd-gtab-accent)}308.fd-gtab-tab:active{transform:scale(0.94);transition-duration:var(--fd-dur-1)}309.fd-gtab-ico,.fd-gtab-ico svg{display:block;width:26px;height:26px;margin:0;padding:0}310.fd-gtab-lbl{display:block;margin:0;padding:0;font:600 10px/1.1 ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif;letter-spacing:0.01em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}311.fd-gtab-tab:focus-visible,.fd-gtab-search:focus-visible{outline:2px solid color-mix(in srgb,#0A84FF 70%,transparent);outline-offset:2px}312.fd-gtab.is-min .fd-gtab-tab[aria-selected="false"]{flex:0 0 0;width:0;padding:0;opacity:0;pointer-events:none}313.fd-gtab.is-min .fd-gtab-lbl{opacity:0;height:0}314.fd-gtab.is-min .fd-gtab-lens{opacity:0}315.fd-gtab-search{flex:none;margin-left:auto !important;display:flex;align-items:center;justify-content:center;width:var(--fd-gtab-h);height:var(--fd-gtab-h);margin:0;padding:0;color:inherit}316.fd-gtab-search .fd-lg-glow{mix-blend-mode:normal}317.fd-gtab--dark .fd-gtab-search{color:#F2F2F7}318.fd-gtab-search svg{display:block}319.fd-gtab.is-min{--fd-gtab-h:52px}320.fd-gtab-bar,.fd-gtab-search{transition:max-width var(--fd-dur-3) var(--fd-ease-out),height var(--fd-dur-3) var(--fd-ease-out),width var(--fd-dur-3) var(--fd-ease-out)}321@media (prefers-reduced-motion: reduce){322 .fd-gtab-bar,.fd-gtab-search,.fd-gtab-tab{transition-duration:0ms}323 .fd-gtab-tab:active{transform:none}324}325@media (prefers-contrast: more){.fd-gtab-lens{background:rgba(0,0,0,0.16);box-shadow:inset 0 0 0 1px rgba(0,0,0,0.6)}.fd-gtab--dark .fd-gtab-lens{background:rgba(255,255,255,0.22);box-shadow:inset 0 0 0 1px #fff}}326`;327 328/* ───────────────────────── demo: music streaming app ───────────────────────── */329 330const ico = (d: ReactNode) => (331 <svg viewBox="0 0 26 26" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round">332 {d}333 </svg>334);335const TABS: GlassTab[] = [336 { id: "home", label: "Home", icon: ico(<path d="M4.5 11.5 13 4.5l8.5 7V21a.5.5 0 0 1-.5.5h-5v-6h-6v6H5a.5.5 0 0 1-.5-.5Z" />) },337 {338 id: "new",339 label: "New",340 icon: ico(341 <>342 <rect x="3.5" y="3.5" width="8" height="8" rx="2" />343 <rect x="14.5" y="3.5" width="8" height="8" rx="2" />344 <rect x="3.5" y="14.5" width="8" height="8" rx="2" />345 <rect x="14.5" y="14.5" width="8" height="8" rx="2" />346 </>,347 ),348 },349 {350 id: "radio",351 label: "Radio",352 icon: ico(353 <>354 <circle cx="13" cy="13" r="2.2" />355 <path d="M8.3 17.7a6.6 6.6 0 0 1 0-9.4M17.7 8.3a6.6 6.6 0 0 1 0 9.4M5.2 20.8a11 11 0 0 1 0-15.6M20.8 5.2a11 11 0 0 1 0 15.6" />356 </>,357 ),358 },359 {360 id: "library",361 label: "Library",362 icon: ico(363 <>364 <path d="M5 4.5v17M9.5 4.5v17" />365 <path d="m14 5.2 3.9-1 4 16.6-3.9 1Z" />366 </>,367 ),368 },369];370 371const ALBUMS = [372 { t: "Night Ferry", a: "Saoirse Vale", c: ["#FF5E3A", "#FF2A68", "#5B1B8C"] },373 { t: "Paper Planets", a: "The Lindqvist Set", c: ["#00C6FB", "#005BEA", "#0B1240"] },374 { t: "Salt & Signal", a: "Moana Reyes", c: ["#F7B733", "#FC4A1A", "#3D0C02"] },375 { t: "Low Orbit", a: "KIMIRA", c: ["#43E97B", "#38F9D7", "#06402B"] },376 { t: "Tender Static", a: "Hollis Park", c: ["#FA709A", "#FEE140", "#7A1F3D"] },377 { t: "Glasshouse", a: "Odette & the Owls", c: ["#A18CD1", "#FBC2EB", "#2C1F4A"] },378 { t: "Copper Sun", a: "Ilyas Brandt", c: ["#F83600", "#F9D423", "#3A0F00"] },379 { t: "Blue Hour Radio", a: "Nadia Okafor", c: ["#4FACFE", "#00F2FE", "#032A4A"] },380];381const TRACKS = [382 ["Lanterns", "Saoirse Vale", "3:41"],383 ["Heliograph", "KIMIRA", "4:12"],384 ["Monday Tides", "Moana Reyes", "2:58"],385 ["Sodium Lights", "Hollis Park", "3:27"],386 ["Ferris", "The Lindqvist Set", "3:05"],387 ["Cartography", "Nadia Okafor", "4:46"],388 ["Small Hours", "Odette & the Owls", "3:33"],389 ["Gold Leaf", "Ilyas Brandt", "3:18"],390];391 392const art = (c: string[], i: number): CSSProperties => ({393 background: [394 `radial-gradient(circle at ${20 + ((i * 23) % 60)}% ${25 + ((i * 41) % 50)}%, ${c[1]} 0 18%, transparent 46%)`,395 `repeating-linear-gradient(${(i * 37) % 180}deg, rgba(255,255,255,0.0) 0 9px, rgba(255,255,255,0.16) 9px 10px)`,396 `linear-gradient(${135 + i * 20}deg, ${c[0]}, ${c[2]})`,397 ].join(","),398});399 400function MusicScreen({ scrollRef, compact }: { scrollRef: RefObject<HTMLDivElement | null>; compact?: boolean }) {401 const [tab, setTab] = useState("home");402 const title = TABS.find((t) => t.id === tab)!.label;403 return (404 <div className={`fd-gtd-app${compact ? " is-compact" : ""}`}>405 <div className="fd-gtd-scroll" ref={scrollRef} tabIndex={0} aria-label={`${title} — scrollable`}>406 <header className="fd-gtd-head">407 <p className="fd-gtd-date">Thursday, 29 October</p>408 <h2 className="fd-gtd-h">{tab === "home" ? "Listen Now" : title}</h2>409 </header>410 <section className="fd-gtd-hero" style={art(ALBUMS[0].c, 3)}>411 <span className="fd-gtd-chip">New album</span>412 <p className="fd-gtd-hero-t">Night Ferry</p>413 <p className="fd-gtd-hero-a">Saoirse Vale · 11 songs · Dolby Atmos</p>414 </section>415 <h3 className="fd-gtd-sh">Made for you</h3>416 <div className="fd-gtd-grid">417 {ALBUMS.map((a, i) => (418 <figure key={a.t} className="fd-gtd-alb">419 <div className="fd-gtd-art" style={art(a.c, i)} />420 <figcaption>421 <b>{a.t}</b>422 <span>{a.a}</span>423 </figcaption>424 </figure>425 ))}426 </div>427 <h3 className="fd-gtd-sh">Top songs this week</h3>428 <ol className="fd-gtd-tracks">429 {TRACKS.map(([t, a, d], i) => (430 <li key={t}>431 <span className="fd-gtd-n">{i + 1}</span>432 <span className="fd-gtd-mini" style={art(ALBUMS[i % ALBUMS.length].c, i)} />433 <span className="fd-gtd-tt">434 <b>{t}</b>435 <span>{a}</span>436 </span>437 <span className="fd-gtd-d">{d}</span>438 </li>439 ))}440 </ol>441 </div>442 <div className="fd-gtd-dock">443 <GlassTabBar tabs={TABS} value={tab} onChange={setTab} minimizeOnScroll={scrollRef} accent="#FA2D48" />444 </div>445 </div>446 );447}448 449export function Demo() {450 const phone = useRef<HTMLDivElement | null>(null);451 const web = useRef<HTMLDivElement | null>(null);452 return (453 <div className="fd-gtd">454 <style>{demoCss}</style>455 <div className="fd-gtd-web" aria-label="Web layout">456 <MusicScreen scrollRef={web} />457 </div>458 <div className="fd-gtd-phone" aria-label="Phone layout">459 <div className="fd-gtd-notch" aria-hidden="true" />460 <MusicScreen scrollRef={phone} compact />461 </div>462 </div>463 );464}465 466const F = `ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif`;467const D = `"SF Pro Display",ui-sans-serif,-apple-system,BlinkMacSystemFont,"Inter","Segoe UI",Roboto,sans-serif`;468const demoCss = `469.fd-gtd,.fd-gtd *,.fd-gtd *::before,.fd-gtd *::after{box-sizing:border-box}470.fd-gtd{position:relative;display:flex;gap:40px;align-items:center;justify-content:center;height:100vh;min-height:720px;width:100%;margin:0;padding:32px;overflow:hidden;471 background:radial-gradient(1200px 600px at 20% 0%,#FFE4EC 0%,transparent 60%),radial-gradient(900px 600px at 100% 100%,#E2ECFF 0%,transparent 60%),#F7F7F9;color:#0B0B0F;font:400 15px/1.4 ${F};letter-spacing:normal}472.fd-gtd-web{position:relative;flex:1 1 auto;max-width:860px;height:min(760px,100%);margin:0;border-radius:28px;overflow:hidden;background:#fff;box-shadow:0 40px 80px -40px rgba(20,20,40,0.35),0 0 0 1px rgba(0,0,0,0.06)}473.fd-gtd-phone{position:relative;flex:none;width:390px;height:min(800px,100%);margin:0;padding:12px;border-radius:56px;background:#0B0B0F;box-shadow:0 40px 80px -30px rgba(20,20,40,0.5),inset 0 0 0 2px #2A2A31}474.fd-gtd-phone > .fd-gtd-app{border-radius:44px}475.fd-gtd-notch{position:absolute;left:50%;top:22px;width:120px;height:34px;margin:0 0 0 -60px;border-radius:20px;background:#000;z-index:5}476.fd-gtd-app{position:relative;height:100%;width:100%;margin:0;overflow:hidden;background:#fff;color:#0B0B0F}477.fd-gtd-scroll{position:absolute;inset:0;overflow-y:auto;overflow-x:hidden;margin:0;padding:28px 32px 140px;scrollbar-width:none;outline:none}478.fd-gtd-scroll::-webkit-scrollbar{display:none}479.fd-gtd-app.is-compact .fd-gtd-scroll{padding:64px 18px 130px}480.fd-gtd-head{margin:0 0 18px;padding:0}481.fd-gtd-date{margin:0 0 2px;font:600 13px/1.2 ${F};letter-spacing:0.02em;text-transform:uppercase;color:#6B6B76}482.fd-gtd-h{margin:0;font:800 2.125rem/1.1 ${D};letter-spacing:-0.025em;color:#0B0B0F}483.fd-gtd-hero{position:relative;display:flex;flex-direction:column;justify-content:flex-end;height:260px;margin:0 0 28px;padding:22px;border-radius:22px;color:#fff;overflow:hidden}484.fd-gtd-app.is-compact .fd-gtd-hero{height:220px}485.fd-gtd-chip{align-self:flex-start;margin:0 0 auto;padding:6px 10px;border-radius:999px;background:rgba(0,0,0,0.28);font:700 11px/1 ${F};letter-spacing:0.06em;text-transform:uppercase;color:#fff}486.fd-gtd-hero-t{margin:0 0 4px;font:800 2.75rem/1 ${D};letter-spacing:-0.035em;color:#fff}487.fd-gtd-hero-a{margin:0;font:500 15px/1.3 ${F};color:rgba(255,255,255,0.88)}488.fd-gtd-sh{margin:0 0 12px;font:700 1.25rem/1.2 ${D};letter-spacing:-0.01em;color:#0B0B0F}489.fd-gtd-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin:0 0 28px}490.fd-gtd-app.is-compact .fd-gtd-grid{grid-template-columns:repeat(2,1fr);gap:14px}491.fd-gtd-alb{display:block;margin:0;padding:0}492.fd-gtd-art{display:block;aspect-ratio:1;border-radius:12px;box-shadow:inset 0 0 0 0.5px rgba(0,0,0,0.12)}493.fd-gtd-alb figcaption{display:block;margin:8px 0 0;padding:0}494.fd-gtd-alb b{display:block;font:600 13px/1.3 ${F};color:#0B0B0F;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}495.fd-gtd-alb span{display:block;font:400 13px/1.3 ${F};color:#6B6B76}496.fd-gtd-tracks{list-style:none;margin:0;padding:0}497.fd-gtd-tracks li{display:flex;align-items:center;gap:12px;margin:0;padding:8px 0;border-bottom:0.5px solid #E7E7EC}498.fd-gtd-n{width:18px;font:600 15px/1 ${F};color:#6B6B76;text-align:center}499.fd-gtd-mini{display:block;width:44px;height:44px;flex:none;border-radius:8px}500.fd-gtd-tt{flex:1;min-width:0;display:flex;flex-direction:column}501.fd-gtd-tt b{font:600 15px/1.3 ${F};color:#0B0B0F}502.fd-gtd-tt span{font:400 13px/1.3 ${F};color:#6B6B76}503.fd-gtd-d{font:500 13px/1 ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace;color:#6B6B76}504.fd-gtd-dock{position:absolute;left:0;right:0;bottom:0;display:flex;justify-content:center;margin:0;padding:0 16px 22px;pointer-events:none;505 background:linear-gradient(180deg,rgba(255,255,255,0),rgba(255,255,255,0.65) 60%);}506.fd-gtd-dock > *{pointer-events:auto}507.fd-gtd-web .fd-gtd-dock .fd-gtab{width:min(520px,100%)}508.fd-gtd-phone .fd-gtd-dock{padding:0 14px 20px}509.fd-gtd-phone .fd-gtd-dock .fd-gtab{width:100%}510.fd-gtd-scroll:focus-visible{outline:2px solid color-mix(in srgb,#007AFF 70%,transparent);outline-offset:-4px}511@media (max-width:1100px){.fd-gtd-web{display:none}}512@media (max-width:480px){513 .fd-gtd{padding:0;min-height:0}514 .fd-gtd-phone{width:100%;height:100%;padding:0;border-radius:0;box-shadow:none;background:#fff}515 .fd-gtd-phone > .fd-gtd-app{border-radius:0}516 .fd-gtd-notch{display:none}517 .fd-gtd-app.is-compact .fd-gtd-scroll{padding-top:28px}518}519`;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.
An iOS 26 switch whose knob swells into clear Liquid Glass while pressed or dragged, then settles back to solid.