GodUIGodUI
85

Anatomy of the Liquid Glass Card

How a rim-banded displacement map, a chromatic triple-displace filter, and a pointer sheen turn backdrop-filter into living glass — with a clean frost fallback when url() is unsupported.

LiquidGlassCard is not a PNG overlay or a static blur. It samples the live DOM behind the panel through backdrop-filter, bends that sample with an SVG displacement map sized to the card, and fringes color at the rim by running the bend at three scales. Children stay crisp above the glass in relative z-raised.

Frost, sheen, rim — three layers

Three absolute layers share the same rounded box:

LayerJob
Frostbackdrop-filter: blur + saturate (+ url(#filter) when supported)
Sheenmix-blend-screen radial that tracks the pointer
Rimstatic inset highlight / shadow for a wet edge
tsx
 
backdropFilter: active
  ? `blur(${blur}px) saturate(${saturation}) url(#${filterId})`
  : `blur(${blur}px) saturate(${saturation})`;
// WebkitBackdropFilter stays blur+saturate only — Safari can't take url()

Defaults: radius={28}, blur={2}, strength={60}, dispersion={0.15}, saturation={1.6}, sheen={0.5}.

Anatomyfrost · sheen · rim map
frost
sheen
rim map

Same rounded box, three jobs. The dashed mid on the map is the neutral band — no bend under your content. (Real maps encode X in R and Y in G; diagrams stay grayscale.)

Frost
backdrop-filter blur + optional url(#filter)
Sheen
pointer-tracked radial · mix-blend-screen
Rim map
band=0.3 — neutral mid, bend only at edges

The map bends the rim, not the center

buildDisplacementMap(w, h, band) paints a data-URL SVG. Under the hood, red encodes horizontal shift and green vertical; mid-gray 128 (#800000 / #008000) means no shift. That channel packing is an SVG filter detail — Learn diagrams stay grayscale (dashed mid = no bend) so the band shape reads without borrowing map hues. With band={0.3} the mid stays neutral so only the perimeter bends — the flat-panel / convex-edge look:

tsx
 
const lo = (0.5 - band / 2).toFixed(3); // 0.350 at band 0.3
const hi = (0.5 + band / 2).toFixed(3); // 0.650
// linearGradient stops: edge → lo (neutral) → hi (neutral) → opposite edge

A ResizeObserver rebuilds the map whenever the card's content box changes, so the rim stays registered to real geometry after fonts load or content reflows.

tsx
 
const map = buildDisplacementMap(size.width, size.height, 0.3);

Chromatic triple displace

RefractionFilter runs feDisplacementMap three times against the same map, isolates R / G / B with feColorMatrix, then recombines with feBlend mode="screen". Dispersion widens the fringe where the bend is strongest. The Learn diagram shows that as three grayscale offsets — the mechanism is the scale difference, not a rainbow UI.

ChannelScale
Redstrength * (1 + dispersion)
Greenstrength
Bluestrength * (1 - dispersion)
tsx
 
<feImage href={map} result="map" preserveAspectRatio="none" />
<feDisplacementMap scale={strength * (1 + dispersion)} result="dispR" />
<feColorMatrix in="dispR" values={RED_CHANNEL} result="red" />
{/* green @ strength, blue @ strength * (1 - dispersion) */}
<feBlend in="red" in2="green" mode="screen" result="rg" />
<feBlend in="rg" in2="blue" mode="screen" />
Dispersionthree scales · then screen-blend

Three grayscale ghosts at different offsets stand in for the R/G/B displaces — hue is not required to teach the fringe. Then feBlend mode="screen".

R
strength × (1 + d)
G
strength
B
strength × (1 − d)

Push strength / dispersion up for a heavier prismatic edge; drop them for subtle frost.

Pointer sheen

On pointermove, the card writes CSS variables as percentages of its own box (not pixels):

tsx
 
node.style.setProperty(
  "--lg-x",
  `${((e.clientX - rect.left) / rect.width) * 100}%`,
);
node.style.setProperty(
  "--lg-y",
  `${((e.clientY - rect.top) / rect.height) * 100}%`,
);

The sheen layer paints radial-gradient(circle at var(--lg-x) var(--lg-y), …) and toggles opacity on enter / leave with transition-opacity duration-200.

Under prefers-reduced-motion, pointer updates are skipped and the sheen layer is motion-reduce:hidden — frost stays, specular dies.

Sheen--lg-x · --lg-y · 200ms fade
CSS vars
--lg-x / --lg-y as %
Fade
transition-opacity duration-200

Chrome-only url(), clean fallback

Live-DOM refraction needs backdrop-filter: url(#id) — Chrome and Edge today. useRefractionSupport probes once:

tsx
 
CSS.supports("backdrop-filter", "url(#a)")

Without support, the SVG filter is omitted and frost is blur + saturate only. Safari and Firefox still get a believable glass panel; they never render as a broken transparent box. Decorative layers and the filter SVG are aria-hidden.

The result

Move across the panel. Watch the rim fringe and the specular follow.

Resultthe real component — move across the panel

One map, three displaces, one CSS sheen — and a frost fallback when the GPU path is unavailable.

On this page

Built with GodUI

Beautifully crafted motion components for modern interfaces.

Star on GitHub