← Back

Building Glass

June 2026 · Inspired and built using guidelines from Aave's “Building Glass for the Web”

Fig. 01 — Refraction

Light bends
at the edges.

The glass isn't drawn over the page — a copy of the page sits on its own layer and gets bent by the lens. A tiny PNG tells every pixel how far to move. Drag the lens over this text; the original beneath stays selectable the whole way.

feImage → feDisplacementMap · scale 44 · chroma 0.20

22px
0.20
0.0px

The research

Apple's Liquid Glass kicked off a wave of web recreations, and almost all of them lean on backdrop-filter: url() with an SVG filter, which only works in Chromium. Aave's write-up describes a technique that works everywhere by flipping the problem: instead of filtering what's behind the glass, you bend the content itself. The effect rests on a single SVG primitive, feDisplacementMap, fed by a small PNG generated on a canvas. The red channel says how far each pixel moves horizontally, the green channel vertically, and a neutral value leaves pixels alone.

The map math comes from the same place most recreations trace back to: a signed distance function for a rounded rectangle. Each pixel's distance to the lens edge drives how hard it bends, and the SDF gradient says which way. Real glass with a flat top bends light hard at the rim and barely at all in the middle, so the bend profile has to fall off fast: full strength at the edge, nearly nothing in the center.

map generation
for each pixel inside the lens:
  s = roundedRectSDF(p)          // distance to the rim
  t = min(-s / bezel, 1)         // 0 at rim, 1 interior
  k = (1 - smoothstep(t)) ** 1.6 // bend: strong rim, flat center
  bend = sdfGradient(p) * k      // sample outward = magnify

R = 128 + bend.x, G = 128 + bend.y

The errors

The displacement itself came together quickly. The three bugs that followed took longer than everything else combined.

1. The bezel ate the lens. The falloff band was so wide that every pixel inside the lens was displacing. The chromatic fringe runs the displacement three times — once per channel at slightly different strengths — so instead of a faint fringe at the rim, every glyph under the glass split into three colored copies. Narrowing the band and sharpening the falloff fixed the center.

2. Neutral gray doesn't exist in 8 bits. The original architecture filtered the entire card and trusted the map's neutral value to leave the background alone. But true neutral is 0.5, and the closest a PNG can store is 128/255 = 0.50196. One pass, that's an invisible 0.1px shift. Three chroma passes at three different scales shift the red, green, and blue channels of the whole background by three different amounts — per-channel misregistration on every edge of the page. The background was visibly fringing with the lens nowhere near it.

3. Patching it made a blue ring. The next attempt clipped the displaced result to the lens with an alpha mask and composited it back over the source. The recombination used arithmetic feComposite, which sums in premultiplied space and clamps alpha. At the mask's antialiased edge that skews the math and draws a crisp colored ring around the capsule. A patch on top of the wrong architecture just moved the artifact.

The fix was in the article the whole time

Re-reading Aave's post properly, the load-bearing sentence is about their switch: they give the glass component “a copy of the track's fill that sits on its own layer and gets bent by the glass.” The original content is never filtered at all. The glass bends a duplicate on its own layer, clipped to the lens. They call it the refractionTarget. The background can't fringe because it never enters the filter pipeline at all. I had read that sentence twice and skimmed past it twice; it took the third bug to make me actually hear it.

jsx
<StageContent />                 // the live page. never filtered.

<div style={{ filter: "url(#lens)", clipPath: lensCapsule }}>
  <StageContent />               // a copy, bent and clipped
</div>

Inside the filter, the fringe splits the displacement into three passes — red strongest, blue gentlest — isolates each channel with feColorMatrix, and recombines with feBlend mode="screen", which is exact addition for channel-disjoint inputs and composites alpha normally.

Findings

Built with React, Framer Motion, and one SVG filter. Technique and guidelines from Aave Labs.