03

August 11, 2026 · View on GitHub

Liquid Glass is not one effect; it is six layers composited in a fixed order. Getting the order and the subtlety right is what separates a convincing material from a muddy blur. Below is the exact stack produced by packages/core/liquid-glass.css + liquid-glass.js, bottom to top.

        ┌─────────────────────────────────────────────┐  z
  (6)   │  your content  (text, icons)                 │  3   ← lifted above all glass layers
        ├─────────────────────────────────────────────┤
  (5)   │  ::before  specular rim + chromatic edge     │  2
        ├─────────────────────────────────────────────┤
  (4)   │  ::after   moving specular highlight         │  1   ← tracks pointer / device motion
        ├─────────────────────────────────────────────┤
  (3)   │  background: hsla tint                       │  0
        ├─────────────────────────────────────────────┤
  (2)   │  backdrop-filter: blur + saturate            │  ↓   ← applied to what's BEHIND the box
  (1)   │  backdrop-filter: url(#displacement)         │  ↓   ← the refraction (see 02)
        └─────────────────────────────────────────────┘
                     ▼ the page content behind the element

isolation: isolate on .lg-glass creates a stacking context so the z-index values below are self-contained and never leak into the page.

Layer 1 — Refraction (the lens)

backdrop-filter: url(#displacementFilter). The SVG filter — built per element by the JS engine from the element's live size and radius — bends the backdrop at the edges. This is the defining layer; without it you have glassmorphism. Controlled by refraction (the displacement scale, in px) and bezel (the band width, in px). See 02 · Optics & Physics for how the map is generated.

Layer 2 — Translucency (blur + saturation)

The same backdrop-filter, chained: blur(var(--lg-blur)) saturate(var(--lg-sat)). Blur softens the refracted backdrop; saturation makes the transmitted color richer so the glass "glows" with the scene behind it. Keep blur low (0–4px) — heavy blur smears away the very refraction detail that defines the material.

Order note. The engine composes blur() saturate() url(#f) left → right onto the backdrop. Putting url() last keeps the refracted edges crisp over the already-blurred field. In CSS the base rule only declares blur + saturate; the JS overwrites the property with the full three-part chain (including url(#id)) once it has generated a per-element map.

Layer 3 — Tint

background: hsla(var(--lg-tint-h), var(--lg-tint-s), var(--lg-tint-l), var(--lg-tint-a)). A thin wash of color. On Apple platforms the tint is content-derived and adaptive; here it is a controllable hue plus a low alpha. Keep alpha ≤ ~0.15, or the pane stops looking like glass and starts looking like a colored card. Semantic helper classes (.lg-tint-blue, .lg-tint-green, etc.) set sensible hue/alpha pairs — see 08 · Design Tokens.

Layer 4 — Moving specular highlight (::after)

A soft radial gradient positioned at var(--lg-lx) var(--lg-ly), blended with mix-blend-mode: screen. The engine updates those two variables on pointermove (and, when tilt: true, from DeviceOrientation), so the glint slides as you move — the hallmark "responds to motion" cue. Its intensity scales with --lg-spec. On .lg-press:active the highlight snaps to the center and brightens, which reads as illumination on touch.

Layer 5 — Specular rim + chromatic edge (::before)

A border-only gradient (drawn into a 1px pad and masked with mask-composite: exclude so only the rim shows) draws a bright edge whose brightest points follow the sweep angle --lg-edge. Layered on top are two faint inset shadows — one cool (--lg-chroma-cool, blue) and one warm (--lg-chroma-warm, pink) — on opposite edges to fake chromatic aberration, the rainbow fringe of real refracted light. This sells "glass" cheaply, without an expensive per-channel displacement pass.

Layer 6 — Depth: adaptive shadow + inner light lines

Everything lives on box-shadow, split into two tokens:

  • --lg-shadow-innerinset 0 1px 1px rgba(255,255,255,.35) plus a fainter bottom inset. These are inner light lines that read as the lit top facet and the dim bottom facet of a glass slab.
  • --lg-shadow-drop0 12px 34px rgba(0,0,0,.28) plus a tighter 0 2px 8px. This is the drop shadow that lifts the pane off the content.

Apple's version deepens the shadow over busy/text backgrounds and lightens it over plain ones to preserve legibility ("adaptive shadow"). To reproduce that fully, sample the backdrop luminance under the element and modulate the shadow alpha — optional; see 06 · Web Implementation §Advanced.

Content layer

.lg-glass > * is forced to z-index: 3 so your text and icons render above every glass layer and stay crisp. Never place content behind the highlights — the point of the material is to hold legible controls, not to bury them.

The CSS in one glance

.lg-glass {
  position: relative;
  border-radius: var(--lg-radius);
  background: hsla(var(--lg-tint-h), var(--lg-tint-s), var(--lg-tint-l), var(--lg-tint-a)); /* L3 */
  backdrop-filter: blur(var(--lg-blur)) saturate(var(--lg-sat));  /* L2 (+ L1 url appended by JS) */
  box-shadow: var(--lg-shadow-inner), var(--lg-shadow-drop);      /* L6 inner light + drop */
  isolation: isolate;
}
.lg-glass::after  { /* L4 moving highlight, mix-blend-mode: screen */ }
.lg-glass::before { /* L5 rim + chromatic edge, masked to the border */ }
.lg-glass > *     { position: relative; z-index: 3; }             /* content */

Tuning cheat-sheet

Want…ChangeWatch out for
Stronger lens / thicker rimrefraction, ↑ bezelBezel is clamped to < half the shortest side
Frostier, softerblurAbove ~4px you start erasing the refraction
More "wet" / shinyspecular (--lg-spec)Above ~1.2 it reads as glare, not glass
More coloredtintOpacity / set tintHueAbove ~0.2 alpha it becomes a colored card
FloatierLarger --lg-shadow-dropToo large detaches it from context
Crisper glassblur, keep refraction moderateNeeds a rich backdrop to show anything
Warmer / cooler dispersionEdit --lg-chroma-warm / --lg-chroma-coolKeep both faint (~0.2 alpha)