Glass Sheet

An inset glass sheet with detents: content peeks around it, and it turns edge-to-edge and more opaque at full height.

  • Liquid Glass
  • Travel & hospitality
  • React only · 0 deps
  • 2 files
Full screen

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.

Install. One command, or copy the file.

$npx shadcn@latest add https://freedesignmd.com/r/glass-sheet.json
  • Byte-identical to the preview
  • No Tailwind or config needed
  • SSR-safe · keyboard accessible
  • Respects reduced motion & transparency
components/liquid-glass/glass-sheet.tsx
1/**
2 * GlassSheet — an iOS 26 Liquid Glass sheet with detents.
3 *
4 * - Half / peek detents are inset from the screen edges with a large corner
5 * radius, so content peeks around them (HIG). The radius stays concentric
6 * with the container (`radius` = container radius − inset).
7 * - Drag the grabber (or anywhere on the header) to move between detents;
8 * release uses the drag velocity to pick a detent and springs there.
9 * - At the full detent the sheet grows to the edges, its corners match the
10 * screen and the glass becomes more opaque for long-form reading.
11 *
12 * Props: detents (fractions of the container height, e.g. [0.2, 0.52, 0.94]),
13 * detent (index, controlled) + onDetentChange, inset (px, default 8),
14 * radius (corner radius at rest, default 36), tone, label (aria-label),
15 * header (always-visible content next to the grabber), children (body).
16 * Must be placed inside a positioned container (position: relative) — it fills
17 * that container, like a sheet inside a screen.
18 * Keyboard: the grabber is a button — Enter/Space cycles detents, Arrow
19 * Up/Down step through them. role="dialog" aria-modal="false".
20 * Reduced motion: detent changes are instant. Reduced transparency/contrast:
21 * opaque surface.
22 */
23import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
24import type { CSSProperties, KeyboardEvent, ReactNode } from "react";
25import { GlassScope, LiquidGlass, createVelocityTracker, useGlassEnv, useSpring } from "./liquid-glass";
26
27export interface GlassSheetProps {
28 detents?: number[];
29 detent: number;
30 onDetentChange: (i: number) => void;
31 inset?: number;
32 radius?: number;
33 tone?: "light" | "dark";
34 label: string;
35 header?: ReactNode;
36 children?: ReactNode;
37 className?: string;
38}
39
40const useIso = typeof window === "undefined" ? useEffect : useLayoutEffect;
41
42export function GlassSheet({ detents = [0.2, 0.52, 0.94], detent, onDetentChange, inset = 8, radius = 36, tone = "light", label, header, children, className }: GlassSheetProps) {
43 const env = useGlassEnv();
44 const host = useRef<HTMLDivElement | null>(null);
45 const sheet = useRef<HTMLElement | null>(null);
46 const body = useRef<HTMLDivElement | null>(null);
47 const H = useRef(0);
48 const [full, setFull] = useState(false);
49
50 const fullIdx = detents.length - 1;
51 // expansion 0 (inset, glassy) → 1 (edge-to-edge, more opaque) over the last detent interval
52 const paint = (hpx: number) => {
53 const s = sheet.current;
54 const total = H.current;
55 if (!s || !total) return;
56 const lo = detents[Math.max(0, fullIdx - 1)] * total;
57 const hi = detents[fullIdx] * total;
58 const e = Math.min(1, Math.max(0, (hpx - lo) / Math.max(1, hi - lo)));
59 const ins = inset * (1 - e);
60 s.style.height = `${Math.max(56, hpx).toFixed(1)}px`;
61 s.style.left = `${ins.toFixed(2)}px`;
62 s.style.right = `${ins.toFixed(2)}px`;
63 s.style.bottom = `${ins.toFixed(2)}px`;
64 s.style.setProperty("--fd-gsh-e", e.toFixed(3));
65 s.style.borderRadius = `${(radius + inset * e).toFixed(2)}px ${(radius + inset * e).toFixed(2)}px ${(radius * (1 - e) + 0).toFixed(2)}px ${(radius * (1 - e)).toFixed(2)}px`;
66 if (e > 0.98 !== fullRef.current) {
67 fullRef.current = e > 0.98;
68 setFull(fullRef.current);
69 }
70 };
71 const fullRef = useRef(false);
72 const hs = useSpring(0, paint, { stiffness: 380, damping: 34, precision: 0.2 });
73
74 const target = useCallback((i: number) => detents[Math.min(Math.max(i, 0), fullIdx)] * H.current, [detents, fullIdx]);
75
76 // measure container
77 useIso(() => {
78 const h = host.current?.parentElement;
79 if (!h) return;
80 const measure = () => {
81 H.current = h.clientHeight;
82 hs.set(target(detentRef.current));
83 };
84 measure();
85 const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(measure) : null;
86 ro?.observe(h);
87 return () => ro?.disconnect();
88 // eslint-disable-next-line react-hooks/exhaustive-deps
89 }, []);
90 const detentRef = useRef(detent);
91 detentRef.current = detent;
92
93 const dragging = useRef(false);
94 useEffect(() => {
95 if (dragging.current || !H.current) return;
96 hs.stiffness = 380;
97 hs.damping = 34;
98 if (env.reducedMotion) hs.set(target(detent));
99 else hs.to(target(detent));
100 }, [detent, target, env.reducedMotion, hs]);
101
102 // drag on the header zone
103 const grab = useRef<HTMLDivElement | null>(null);
104 useEffect(() => {
105 const g = grab.current;
106 if (!g) return;
107 const vt = createVelocityTracker();
108 let pid = -1, y0 = 0, h0 = 0, moved = false;
109 const down = (e: PointerEvent) => {
110 if (e.button !== 0) return;
111 pid = e.pointerId;
112 y0 = e.clientY;
113 h0 = hs.value;
114 moved = false;
115 vt.reset();
116 vt.push(0, e.clientY);
117 // follow the gesture on window: the finger leaves the grab zone immediately
118 window.addEventListener("pointermove", move);
119 window.addEventListener("pointerup", up);
120 window.addEventListener("pointercancel", up);
121 };
122 const move = (e: PointerEvent) => {
123 if (e.pointerId !== pid) return;
124 const dy = y0 - e.clientY;
125 if (!moved && Math.abs(dy) < 4) return;
126 if (!moved) {
127 moved = true;
128 dragging.current = true;
129 g.classList.add("is-dragging");
130 }
131 vt.push(0, e.clientY);
132 const min = target(0), max = target(fullIdx);
133 let h = h0 + dy;
134 if (h > max) h = max + (h - max) * 0.2;
135 if (h < min) h = min - (min - h) * 0.3;
136 hs.stiffness = 1200;
137 hs.damping = 70;
138 if (env.reducedMotion) hs.set(h);
139 else hs.to(h);
140 };
141 const up = (e: PointerEvent) => {
142 if (e.pointerId !== pid) return;
143 pid = -1;
144 window.removeEventListener("pointermove", move);
145 window.removeEventListener("pointerup", up);
146 window.removeEventListener("pointercancel", up);
147 if (!moved) return;
148 g.classList.remove("is-dragging");
149 dragging.current = false;
150 const { vy } = vt.get();
151 const projected = hs.value - vy * 0.18; // fling
152 let best = 0, bd = Infinity;
153 detents.forEach((d, i) => {
154 const dd = Math.abs(d * H.current - projected);
155 if (dd < bd) (bd = dd), (best = i);
156 });
157 hs.stiffness = 380;
158 hs.damping = 32;
159 if (env.reducedMotion) hs.set(target(best));
160 else hs.to(target(best), -vy);
161 if (best !== detentRef.current) onDetentChange(best);
162 // eat the click that ends a drag
163 const stop = (ev: Event) => {
164 ev.stopPropagation();
165 ev.preventDefault();
166 };
167 g.addEventListener("click", stop, { capture: true, once: true });
168 setTimeout(() => g.removeEventListener("click", stop, { capture: true }), 0);
169 };
170 g.addEventListener("pointerdown", down);
171 return () => {
172 g.removeEventListener("pointerdown", down);
173 window.removeEventListener("pointermove", move);
174 window.removeEventListener("pointerup", up);
175 window.removeEventListener("pointercancel", up);
176 };
177 }, [detents, fullIdx, target, onDetentChange, env.reducedMotion, hs]);
178
179 const onGrabKey = (e: KeyboardEvent<HTMLButtonElement>) => {
180 if (e.key === "ArrowUp") {
181 e.preventDefault();
182 onDetentChange(Math.min(fullIdx, detent + 1));
183 } else if (e.key === "ArrowDown") {
184 e.preventDefault();
185 onDetentChange(Math.max(0, detent - 1));
186 }
187 };
188
189 return (
190 <GlassScope>
191 <div ref={host} className={["fd-gsh-host", `fd-gsh--${tone}`, className ?? ""].filter(Boolean).join(" ")}>
192 <style>{css}</style>
193 <LiquidGlass
194 ref={sheet}
195 className={`fd-gsh${full ? " is-full" : ""}`}
196 variant="regular"
197 tone={tone}
198 radius="auto"
199 bezel={28}
200 role="dialog"
201 aria-modal="false"
202 aria-label={label}
203 style={{ "--fd-gsh-r": `${radius}px` } as CSSProperties}
204 >
205 <div ref={grab} className="fd-gsh-grab">
206 <button
207 type="button"
208 className="fd-gsh-handle"
209 aria-label={`Resize sheet — ${["collapsed", "half height", "full height"][Math.min(detent, 2)] ?? `detent ${detent + 1}`}`}
210 onClick={() => onDetentChange(detent >= fullIdx ? 0 : detent + 1)}
211 onKeyDown={onGrabKey}
212 >
213 <span aria-hidden="true" />
214 </button>
215 {header}
216 </div>
217 <div ref={body} className="fd-gsh-body" aria-hidden={detent === 0 || undefined}>
218 {children}
219 </div>
220 </LiquidGlass>
221 </div>
222 </GlassScope>
223 );
224}
225
226const css = `
227.fd-gsh-host,.fd-gsh-host *,.fd-gsh-host *::before,.fd-gsh-host *::after{box-sizing:border-box}
228.fd-gsh-host{position:absolute;inset:0;margin:0;padding:0;pointer-events:none;z-index:4}
229.fd-gsh{position:absolute !important;left:8px;right:8px;bottom:8px;height:50%;display:flex;flex-direction:column;margin:0;padding:0;overflow:hidden;pointer-events:auto;will-change:height;text-align:left;
230 --fd-lg-shadow:0 -10px 40px -10px rgba(0,0,0,0.28),0 0 0 0.5px rgba(0,0,0,0.04);
231 color:#0B0B0F;font:400 15px/1.4 ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif}
232.fd-gsh--dark .fd-gsh{color:#F2F2F7}
233/* more opaque as it expands: fill alpha .3 → .86 */
234.fd-gsh .fd-lg-fill{background:rgba(250,250,252,calc(0.5 + var(--fd-gsh-e,0) * 0.44))}
235.fd-gsh--dark .fd-gsh .fd-lg-fill{background:rgba(28,28,32,calc(0.5 + var(--fd-gsh-e,0) * 0.42))}
236.fd-gsh-grab{position:relative;flex:none;margin:0;padding:0 20px 8px;touch-action:none;cursor:grab;user-select:none;-webkit-user-select:none}
237.fd-gsh-grab.is-dragging,.fd-gsh-grab.is-dragging *{cursor:grabbing}
238.fd-gsh-handle{display:flex;justify-content:center;align-items:center;width:100%;height:22px;margin:0;padding:0;border:0;background:none;cursor:inherit;-webkit-appearance:none;appearance:none;border-radius:8px}
239.fd-gsh-handle span{display:block;width:38px;height:5px;margin:0;border-radius:3px;background:rgba(11,11,15,0.22)}
240.fd-gsh--dark .fd-gsh-handle span{background:rgba(255,255,255,0.32)}
241.fd-gsh-handle:focus-visible{outline:2px solid color-mix(in srgb,#0A84FF 70%,transparent);outline-offset:-2px}
242.fd-gsh-body{position:relative;flex:1;min-height:0;margin:0;padding:0 20px 24px;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:none}
243.fd-gsh-body::-webkit-scrollbar{display:none}
244@media (prefers-reduced-transparency: reduce){.fd-gsh .fd-lg-fill{background:#F2F2F7}.fd-gsh--dark .fd-gsh .fd-lg-fill{background:#1C1C22}}
245@media (prefers-contrast: more){.fd-gsh .fd-lg-fill{background:#fff}.fd-gsh--dark .fd-gsh .fd-lg-fill{background:#000}}
246`;
247
248/* ───────────────────────── demo: maps / hotel booking ───────────────────────── */
249
250const ROOMS = [
251 { n: "Deluxe King, harbour view", d: "32 m² · Breakfast for two · Free cancellation until 10 Jun", p: 318, c: ["#FFB199", "#FF0844"] },
252 { n: "Junior Suite with terrace", d: "48 m² · Terrace with daybeds · Late checkout", p: 452, c: ["#84FAB0", "#0BA360"] },
253 { n: "Twin Classic, courtyard", d: "26 m² · Quiet side · Pay at hotel", p: 246, c: ["#A1C4FD", "#3B5BDB"] },
254];
255
256export function Demo() {
257 const [detent, setDetent] = useState(1);
258 const [room, setRoom] = useState(0);
259 return (
260 <div className="fd-gshd">
261 <style>{demoCss}</style>
262 <div className="fd-gshd-device">
263 <MapLayer />
264 <div className="fd-gshd-pin" style={{ left: "58%", top: "36%" }} aria-hidden="true">
265 <span>€318</span>
266 </div>
267 <div className="fd-gshd-pin is-dim" style={{ left: "28%", top: "22%" }} aria-hidden="true">
268 <span>€204</span>
269 </div>
270 <div className="fd-gshd-pin is-dim" style={{ left: "76%", top: "18%" }} aria-hidden="true">
271 <span>€389</span>
272 </div>
273 <GlassSheet
274 detent={detent}
275 onDetentChange={setDetent}
276 label="Casa Marèa, Lisbon"
277 header={
278 <div className="fd-gshd-head">
279 <div className="fd-gshd-thumb" aria-hidden="true" />
280 <div className="fd-gshd-titles">
281 <p className="fd-gshd-title">Casa Marèa</p>
282 <p className="fd-gshd-sub">Boutique hotel · Alfama, Lisbon · ★ 4.8</p>
283 </div>
284 <button type="button" className="fd-gshd-x" aria-label="Close">
285 <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2.6" strokeLinecap="round" aria-hidden="true">
286 <path d="M6 6l12 12M18 6 6 18" />
287 </svg>
288 </button>
289 </div>
290 }
291 >
292 <div className="fd-gshd-chips" role="list">
293 {["14 – 17 Jun", "2 guests", "3 nights", "4 min to tram 28"].map((c) => (
294 <span key={c} role="listitem" className="fd-gshd-chip">
295 {c}
296 </span>
297 ))}
298 </div>
299 <p className="fd-gshd-h">Choose your room</p>
300 <ul className="fd-gshd-rooms" role="radiogroup" aria-label="Rooms">
301 {ROOMS.map((r, i) => (
302 <li key={r.n}>
303 <button type="button" role="radio" aria-checked={room === i} className="fd-gshd-room" onClick={() => setRoom(i)}>
304 <span className="fd-gshd-room-img" style={{ background: `linear-gradient(135deg, ${r.c[0]}, ${r.c[1]})` }} aria-hidden="true" />
305 <span className="fd-gshd-room-txt">
306 <b>{r.n}</b>
307 <span>{r.d}</span>
308 </span>
309 <span className="fd-gshd-room-p">
310 €{r.p}
311 <small>/night</small>
312 </span>
313 </button>
314 </li>
315 ))}
316 </ul>
317 <button type="button" className="fd-gshd-cta">
318 Reserve · €{ROOMS[room].p * 3} total
319 </button>
320 <p className="fd-gshd-h">About</p>
321 <p className="fd-gshd-p">
322 Twelve rooms in a restored 1890s merchant’s house above the Tagus. Azulejo-tiled stairwell, a rooftop bar that opens at 17:00 and a
323 breakfast of pastéis de nata from the bakery next door.
324 </p>
325 <p className="fd-gshd-p">Check-in from 15:00 · Check-out until 11:00 · No lift (3 floors) · Pets on request.</p>
326 </GlassSheet>
327 </div>
328 </div>
329 );
330}
331
332function MapLayer() {
333 return (
334 <svg className="fd-gshd-map" viewBox="0 0 400 860" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
335 <rect width="400" height="860" fill="#EDE9E0" />
336 <path d="M0 560 C80 520 150 600 230 560 S360 520 400 540 V860 H0 Z" fill="#9FD3F2" />
337 <path d="M0 600 C90 570 160 640 240 600 S360 570 400 590" fill="none" stroke="#B9E1F7" strokeWidth="6" />
338 <g fill="#C9E7B5">
339 <path d="M30 80h90v70H30z" />
340 <path d="M260 230l80 10-10 80-80-10z" />
341 <circle cx="150" cy="420" r="38" />
342 </g>
343 <g fill="#E1DCD1">
344 {Array.from({ length: 60 }, (_, i) => {
345 const x = (i * 67) % 380;
346 const y = ((i * 131) % 520) + 10;
347 return <rect key={i} x={x} y={y} width={18 + (i % 4) * 6} height={14 + (i % 3) * 6} rx="2" />;
348 })}
349 </g>
350 <g fill="none" stroke="#FFFFFF" strokeLinecap="round">
351 <path d="M-10 200 C100 180 200 260 410 220" strokeWidth="14" />
352 <path d="M120 -10 C140 200 100 380 180 560" strokeWidth="12" />
353 <path d="M-10 420 C120 400 250 470 410 430" strokeWidth="10" />
354 <path d="M300 -10 C280 140 330 300 290 560" strokeWidth="9" />
355 <path d="M-10 320 L410 300" strokeWidth="6" />
356 <path d="M60 -10 L40 560" strokeWidth="5" />
357 <path d="M210 -10 L230 560" strokeWidth="5" />
358 </g>
359 <g fill="none" stroke="#F6C445" strokeLinecap="round">
360 <path d="M-10 200 C100 180 200 260 410 220" strokeWidth="5" />
361 </g>
362 <path d="M120 -10 C140 200 100 380 180 560" fill="none" stroke="#E5484D" strokeWidth="3" strokeDasharray="1 7" strokeLinecap="round" />
363 <g fontFamily="ui-sans-serif,-apple-system,Inter,sans-serif" fontWeight="600" fill="#6B6B76" fontSize="10" letterSpacing="0.06em">
364 <text x="20" y="250">ALFAMA</text>
365 <text x="240" y="380">BAIXA</text>
366 <text x="40" y="640" fill="#3B82C4">RIO TEJO</text>
367 </g>
368 </svg>
369 );
370}
371
372const F = `ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif`;
373const D = `"SF Pro Display",ui-sans-serif,-apple-system,BlinkMacSystemFont,"Inter","Segoe UI",Roboto,sans-serif`;
374const demoCss = `
375.fd-gshd,.fd-gshd *,.fd-gshd *::before,.fd-gshd *::after{box-sizing:border-box}
376.fd-gshd{min-height:100vh;width:100%;margin:0;padding:28px 16px;display:flex;align-items:center;justify-content:center;
377 background:radial-gradient(900px 600px at 20% 10%,#E6F0FF,transparent 60%),radial-gradient(900px 600px at 90% 90%,#FFEFE0,transparent 60%),#F2F2F7;color:#0B0B0F;font:400 15px/1.4 ${F};letter-spacing:normal}
378.fd-gshd-device{position:relative;width:390px;max-width:100%;height:min(820px,calc(100vh - 56px));min-height:620px;margin:0;border-radius:52px;overflow:hidden;background:#EDE9E0;
379 box-shadow:0 0 0 10px #0B0B0F,0 0 0 11px #2A2A31,0 50px 100px -40px rgba(20,20,40,0.55)}
380.fd-gshd-map{position:absolute;inset:0;display:block;width:100%;height:100%}
381.fd-gshd-pin{position:absolute;margin:0;padding:0;transform:translate(-50%,-100%)}
382.fd-gshd-pin span{display:block;margin:0;padding:7px 11px;border-radius:999px;background:#0B0B0F;color:#fff;font:700 13px/1 ${F};box-shadow:0 6px 16px -4px rgba(0,0,0,0.4)}
383.fd-gshd-pin::after{content:"";display:block;width:10px;height:10px;margin:-5px auto 0;background:#0B0B0F;transform:rotate(45deg);border-radius:0 0 2px 0}
384.fd-gshd-pin.is-dim span{background:#fff;color:#0B0B0F}
385.fd-gshd-pin.is-dim::after{background:#fff}
386.fd-gshd-head{display:flex;align-items:center;gap:12px;margin:2px 0 0;padding:0}
387.fd-gshd-thumb{flex:none;width:52px;height:52px;margin:0;border-radius:14px;background:
388 linear-gradient(#fff 0 0) 20% 55%/16% 22% no-repeat,linear-gradient(#fff 0 0) 60% 50%/16% 26% no-repeat,
389 linear-gradient(180deg,#FFB86B,#E5484D 60%,#6E2B5A)}
390.fd-gshd-titles{flex:1;min-width:0;margin:0;padding:0}
391.fd-gshd-title{margin:0;font:700 1.375rem/1.15 ${D};letter-spacing:-0.02em;color:inherit}
392.fd-gshd-sub{margin:2px 0 0;font:500 13px/1.3 ${F};color:#6B6B76;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
393.fd-gshd-x{flex:none;display:grid;place-items:center;width:30px;height:30px;margin:0;padding:0;border:0;border-radius:50%;background:rgba(120,120,128,0.16);color:#6B6B76;cursor:pointer;-webkit-appearance:none;appearance:none}
394.fd-gshd-x svg{display:block}
395.fd-gshd-x:focus-visible,.fd-gshd-room:focus-visible,.fd-gshd-cta:focus-visible{outline:2px solid color-mix(in srgb,#0A84FF 70%,transparent);outline-offset:2px}
396.fd-gshd-chips{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0 18px;padding:0}
397.fd-gshd-chip{display:inline-block;margin:0;padding:7px 11px;border-radius:999px;background:rgba(120,120,128,0.12);font:600 13px/1 ${F};color:#1C1C22}
398.fd-gshd-h{margin:0 0 10px;font:700 17px/1.2 ${F};letter-spacing:-0.01em;color:inherit}
399.fd-gshd-rooms{list-style:none;margin:0 0 14px;padding:0;display:flex;flex-direction:column;gap:8px}
400.fd-gshd-rooms li{margin:0;padding:0}
401.fd-gshd-room{display:flex;align-items:center;gap:12px;width:100%;margin:0;padding:10px;border:0;border-radius:20px;background:rgba(255,255,255,0.62);color:#0B0B0F;text-align:left;cursor:pointer;-webkit-appearance:none;appearance:none;
402 font:inherit;box-shadow:inset 0 0 0 1px rgba(0,0,0,0.05);transition:box-shadow 240ms cubic-bezier(0.22,1,0.36,1),background-color 240ms}
403.fd-gshd-room[aria-checked="true"]{background:#fff;box-shadow:inset 0 0 0 2px #007AFF}
404.fd-gshd-room-img{flex:none;width:56px;height:56px;border-radius:12px;display:block}
405.fd-gshd-room-txt{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
406.fd-gshd-room-txt b{font:650 15px/1.25 ${F};letter-spacing:-0.01em}
407.fd-gshd-room-txt span{font:400 12px/1.35 ${F};color:#6B6B76}
408.fd-gshd-room-p{flex:none;font:700 17px/1 ${F};text-align:right}
409.fd-gshd-room-p small{display:block;margin-top:3px;font:500 11px/1 ${F};color:#6B6B76}
410.fd-gshd-cta{display:block;width:100%;height:52px;margin:0 0 22px;padding:0;border:0;border-radius:999px;background:#007AFF;color:#fff;font:600 17px/1 ${F};letter-spacing:-0.01em;cursor:pointer;-webkit-appearance:none;appearance:none;box-shadow:0 10px 24px -10px rgba(0,122,255,0.7)}
411.fd-gshd-p{margin:0 0 12px;font:400 15px/1.5 ${F};color:#3A3A44}
412@media (max-width:480px){
413 .fd-gshd{padding:0}
414 .fd-gshd-device{width:100%;height:100vh;min-height:0;border-radius:0;box-shadow:none}
415}
416`;

More Liquid Glass. Same file contract.

Liquid Glass

The material itself: a lens that refracts what’s behind it, with squircle bevel, specular rim, dispersion and spring physics.

Glass Button

glass, glassProminent, clear and clear-prominent capsules in four sizes, with a springy press and a glow from the touch point.

Glass Tab Bar

Floating iOS 26 tab bar: a liquid lens springs between tabs, search sits apart, and it minimizes as you scroll.