SuperAPI AI Directory

liquid-glass

General & other

View repository

Updated 10-16 00:00

About this skill

Liquid Glass

Reusable refraction glass for components/grid-wallet-demo. Full docs: components/grid-wallet-demo/src/components/liquid-glass/README.md — read it before implementing anything non-trivial.

The rule that prevents 90% of the pain

The glass refracts its own CHILDREN, not the page behind it. There is no "frost whatever is behind me" mode (backdrop-filter can't refract portably). To bend a background you put a copy of it inside the glass. So:

  • Glass over a known backdrop (color / gradient / image / pattern) → use GlassOver, which makes the copy for you.
  • Glass over live UI behind a small surface (over a static screen) → achievable: feed the glass a positioned copy of that UI. See "Refract live UI behind a small surface" in the gotchas / README.
  • Glass over arbitrary scrolling/animating DOM → not achievable. Tell the user.

Exception: a big frosted surface (sheet/modal) should NOT refract — the per-frame SVG filter tanks Safari. Use FrostPanel (GPU backdrop-filter + a specular edge). Refraction is for the small, tactile elements.

Skipping this gives a flat panel that doesn't lens (the classic failure).

Pick the right entry point

Need Use
Glass button / tab bar / card / chip over a known bg GlassOver (easiest)
Refract content you already render, or custom backdrop wiring Glass (= LiquidGlass)
A big frosted sheet/modal (no refraction — fast on Safari) FrostPanel (BottomSheet uses it)
A small surface that refracts the (static) screen behind it GlassOver's backdropNode
A lens over the LIVE aurora that must work on Safari WebGL: AuroraLensButton (circle) / AuroraLensPanel (rect squircle)
The phone preview's WebGL stage glass-gl/StageGL — bespoke, don't reuse

SVG vs shader (which renderer): default to the SVG path (Glass / GlassOver) for anything made of live DOM — buttons, tab bars, cards, text, layouts (keeps content selectable/clickable). The shader / WebGL path is only needed when you're refracting content that is not live DOM — a <canvas> drawing (e.g. a generated QR) or a <video> (Safari won't SVG-filter those). There is no auto-switching and no general "glass over a canvas/video" yet: the only WebGL renderer (StageGL) is bespoke to the phone and re-derives the math in GLSL. If a canvas/video glass actually comes up, generalize StageGL (or build a map-fed WebGL glass) on demand — don't assume a drop-in exists.

Minimal usage

import { GlassOver } from '@/components/liquid-glass';

const BG = 'linear-gradient(135deg, #5b9dff, #b07cff)';

<div style={{ background: BG }}>
  <GlassOver backdrop={BG} radius={16} depth={10} scale={18} cornerSmoothing={0.6}>
    <span style={{ display: 'block', padding: '12px 22px', color: '#fff', fontWeight: 600 }}>
      Settings
    </span>
  </GlassOver>
</div>

backdrop = the same CSS background the surface sits on (gets refracted). children = content on top (not refracted). Any GlassConfig field is a prop. Start from a preset (DEFAULT_GLASS, PHONE_SHELL_GLASS) and override. Frosted surfaces (FrostPanel) use the slimmer FrostConfig, not GlassConfig.

Must-know gotchas (details in the README)

  • Hover/resize: animate transform: scale(), never width/height/radius — the displacement map re-bakes on shape change (jank).
  • Refraction needs texture: over a flat color you only see specular/edge rim.
  • Tiny elements: lower depth/scale or they over-distort.
  • specularRotation is directional (45° puts a bright highlight on the top-left/bottom-right corners) — rotate or lower edgeStrength to move it.
  • corner-shape: squircle is Chromium-only (graceful circular fallback).
  • Outer drop shadow → round the wrapper. A glass surface clips itself with overflow: hidden, so an outer box-shadow has to live on a wrapper around the glass (inside, the clip eats it). box-shadow traces that wrapper's own border-radius/corner-shape, not the glass child's — so an unrounded wrapper gives a square halo around round glass. Give the shadow wrapper the same border-radius + corner-shape as the glass (see GlassSymbolButton / GlassWindowButtonGroup .root). Same rule for any border/background painted on a wrapper. Debug heuristic: a square around glass = a rounding-unaware layer.
  • Tiled backgrounds: pass GlassOver's backdropOffset={{x,y}} to align.
  • Circles & pills auto-lens radially (Aave's production circle/pill model, detected from the geometry — no flag). splay doesn't apply to them; the dome applies to circles only. Don't fight it with per-axis expectations.
  • Live/changing content inside a lens is a Safari minefield — stale filter-output caching, composited descendants escaping the lens unfiltered (translateZ(0)/will-change/motion's filter: blur(0px) leftovers), canvas/video killing the filter, and a LAYOUT-bounds source-size ceiling. Read "Safari field notes" in the README before attempting; the full experiment is parked on pat/scroll-refraction-experiment.
  • WebKit renders SVG displacement over a copied subtree as EMPTY output at notification size (confirmed June 2026 — see the README's "Confirmed dead end" postmortem). Don't retry it: use the WebGL lenses for Safari refraction and gate the SVG path to Chromium. Squircle-rect corner rules for Safari (map smoothing with cornerRadii, overlay border-radius intersection, one-element frost, no drop-shadow wrappers) are in the same section.
  • Verifying a bend over a soft backdrop: displacement is invisible without texture — draw a sharp debug grid through the bent coordinates to check.
  • Big sheets/modals don't refract — they frost. A large displacement lens re-runs its whole SVG filter every frame while the surface animates, which tanks Safari (CPU feDisplacementMap). Use FrostPanel (GPU backdrop-filter + a static specular edge); keep refractive Glass/GlassOver for the small buttons on top. Example: apps/shared/BottomSheet (FrostPanel) → aurora/PasskeySheet.
  • Refract live UI behind a small surface: pass a copy of the behind-UI as GlassOver's backdropNode, anchored to align, and counter-animate it against any slide so it stays put while the glass slides over it. Behind-UI must be static while open. (Not for big sheets — see above.)

When tuning

There's a live tuning panel: run the demo (npm run dev, port 4000), open the App panel, switch it to swag mode. Sliders map 1:1 to GlassConfig.