Liquid glass on the web,
at a third of the GPU.
Glass that bends the real page behind it — text, pictures, anything in the DOM — not a blurred screenshot. web-glass draws every surface with its own backdrop filter, renders its maps on the CPU when there is no WebGPU, and was measured at every step. The card below is live.

Drag the lens. Everything on this card is live glass over the picture behind it.
Upstream needs a WebGPU adapter to draw glass at all. web-glass falls back to computing the maps on the CPU, and the page looks the same: the largest difference in a full-page screenshot is 3 of 255 levels.
Chrome on an Apple-silicon Mac, 1180×820 CSS px at 2× density, the full control set, 3 s per animation. GPU is the whole machine’s utilisation from the macOS accelerator counters; GPU thread is Chrome’s own busy time from a trace.
Twenty-six seconds of glass
Recorded in Chrome at 1280×800, 2×, from the demo app in the repository: tabs switching the backdrop, a drifting lens, the refraction slider, the material switch and a menu. Every picture in it is drawn by code.
Every control, live
Each card is its own small scene over a different backdrop — a rendered picture, a page of text, soft colour. Press, drag and switch; the state underneath is ordinary React.

GlassButtonPress and hold: the glass grows under the pointer.GlassTabsSelected: Read. The indicator is its own glass.
GlassSwitch · GlassSliderWi-Fi on, focus off, volume 62.
GlassMenuNothing chosen yet.
GlassToolbarBold is on.
GlassSurfaceDrag either panel. Clear on the left, Regular on the right, over text and a picture.Where the GPU time went
The same page, the same controls, the same animations, drawn by upstream’s whole-scene filter and by web-glass’s per-surface filters, back to back in one run.
Same look — SSIM, old path vs new
1.000 is identical. What remains sits on anti-aliased rims, where two filter paths round differently.
CPU maps vs WebGPU maps
Whole-page screenshots with the maps forced onto each renderer. No pixel differs by more than 16 levels; most do not differ at all.
Safari is a different problem. It filters the content under glass on the CPU, so the cost of moving glass grows with the scene: a panel gliding over an 820×700 scene holds 60 fps, the same panel over a 1640×1200 scene gets about 26. Glass that stays still costs nothing, about 5% of one core.
Five changes, one look
Before the script runs, when a scene reports an error, or when you ask your system for reduced transparency, the same controls render as CSS glass or as solid surfaces. Switch this page between them:
Same API as upstream
web-glass keeps the package name and every prop of liquid-glass, so it drops into code written for upstream. It ships as a tarball; the source is available on request.
$ npm install ./glass-sdk-liquid-glass-0.0.3-webglass.1.tgzimport { GlassScene, GlassContent, GlassSurface, GlassSwitch } from "@glass-sdk/liquid-glass";
import "@glass-sdk/liquid-glass/styles.css";
<GlassScene material="regular" style={{ height: 320, transform: "translateZ(0)" }}>
<GlassContent>
<img src="/ridge.webp" alt="" style={{ width: "100%", height: "100%", objectFit: "cover" }} />
</GlassContent>
<GlassSurface radius={28} style={{ position: "absolute", left: 24, top: 24, padding: 20 }}>
<label>Notifications <GlassSwitch defaultChecked /></label>
</GlassSurface>
</GlassScene>React 19. The translateZ(0) on the scene keeps WebKit from painting a scene blank when it sits far down a page.