<LiquidGlass> that works everywhere

The refraction runs on a single SVG filter, so it works in every browser. WebGL is used only for content a filter can't reach.

Render paths

By default, glass is an SVG filter over live DOM: an feDisplacementMap displaces pixels the browser has already rendered, so the content stays selectable and clickable. It works in every browser, Safari and Firefox included, with no fallback.

WebGL handles content with no live DOM to filter. Frost is a CSS blur, used only when WebGL is unavailable.

Press and hold to send emojis up through all three at once: the same stream, treated three ways.

Live, selectable text.

Displaced by one SVG filter. The link still works.

SVG · feDisplacementMap on live DOM
WebGL
WebGL · for a canvas or video
Frost
Frost · CSS backdrop blur
How it works

The refraction is a single feDisplacementMap, applied to the live element with filter: url(). It shifts pixels the browser has already rendered, which is why the content stays selectable and clickable. Chrome, Safari, and Firefox all produce the same output, so there is no fallback to maintain. (Most implementations use backdrop-filter: url() instead, which only Chromium supports.)

Each pixel is shifted by the map's R (x) and G (y) values, so the grid bends under the lens.
<filter id="glass" primitiveUnits="userSpaceOnUse">
  <feImage href="lens.png" result="map" />
  <feDisplacementMap in="SourceGraphic" in2="map"
      scale="24" xChannelSelector="R" yChannelSelector="G" />
</filter>

.card { filter: url(#glass); }  /* bends live DOM */

Performance and Safari notes.

  • A new filter id on every change. Safari caches filter output by id, and a reused id freezes a moving lens; a fresh id on each rebuild keeps it live.
  • Moving reuses the map. Dragging just repositions the <feImage>; the map regenerates only when the lens changes shape.
  • The map is four-way symmetric. One quadrant is computed and mirrored into the other three, about a quarter of the work.
  • The specular highlight is lens-sized, not computed across the whole filter region.

WebGL is used only when there is no live DOM to filter. That applies to a canvas (the QR below) and to a <video>, which Safari won't filter. Frost is a backdrop-filter: blur(), used only when WebGL is unavailable.

Animating through the glass without rebuilding the map. Generating the map is the costly step: a per-pixel signed-distance field and a dome integral, computed once up front. Rebuilding it every frame would stutter, so the optics stay fixed and only the source the filter reads gets animated. The browser re-runs the same displacement over fresh pixels each frame and never touches the map. The render-path cards above work exactly this way: the emojis are particles on a <canvas>, and only the canvas redraws per frame while each path (SVG, WebGL, Frost) re-processes it untouched. It's how Aave's reaction button bends a stream of emojis at 60fps.

lens.style.filter = 'url(#glass)';   // static map: built once, never rebuilt

function frame() {
  drawParticles(ctx);                // cheap 2D redraw of the source
  requestAnimationFrame(frame);      // the glass re-refracts it, free
}

Glass anything

The engine behind the typeface component (below), pointed at any alpha source: an SVG logo, a sparkle, an orb of emojis swimming amongst themselves, a meme.

The map is shaped like the source's opaque pixels and the glass clips to that silhouette, so the rim glint traces the artwork while the interior refracts whatever fills it.

The SVG-first core is one filter over live DOM, so it drops into vanilla, React, Vue, Svelte, Angular, or Astro all the same.

Performance

A glass surface is an SVG filter over the content behind it, and the browser caches that filter while the content holds still. At rest the glass is essentially free; it costs a filter pass only when what’s behind it changes.

How often that happens decides the cost: a card over a static hero never pays it, while a panel over scrolling text or a video re-filters as it moves.

The demo below is the proof, trade-offs included. Flip Source and Animate to watch each case live.

fps

Measuring your refresh rate…

  • Static content (a card over a hero, a nav bar): the filter caches, so glass is essentially free.
  • Changing DOM (scrolling, live text): one filter pass each time it repaints.
  • Canvas or video: re-filtered every frame no matter what; that’s the case the WebGL path exists for.
Components

Drop-in glass UI. Tune any of them live in the Glass Tuner.

Lens

The lens drifts across the card on its own; move your cursor in and it snaps to the pointer and follows. The text, grid, and colour chips beneath it are live DOM, refracted in place.

LIQUID · GLASS

Refraction Sample 04

hover to grab · drifts when idle

00:24:18:06 · F/2.8 · ISO 320

Glass typeface

The letterforms are rasterized into a displacement map with the same encoding the lens uses.

An SVG filter refracts the fill along each letter's bevel and lights a glint that traces the strokes. The panel behind is animated, so the glass has moving content to bend.

The map is rasterized from the element's computed font, so any loaded typeface works — and the glass stays live DOM: click the text and type.

Refraction

Segmented control

A glass pill marks the active option and refracts the label beneath it.

Glass on interaction

Glass refraction is subtle on a small control. It's easy to miss at rest, and it can blur the value underneath.

So for toggles, switches, and sliders it's better kept for the moment of interaction: they sit as a solid fill, scale up and turn to glass while pressed or dragged, then settle back to solid on release.

Ripple button

A press sends a refraction ripple out from that point, tinted with the palette. It runs on the SVG filter, with no WebGL. Toggle “Show render paths” to confirm.

Content morph

Change a glass button's label and it reshapes to fit. The refraction stretches through the morph, the old label lifts out as the new one rises in, and one displacement map is reused the whole way.

Try the chips, or click the button.

Dropdown

The panel refracts a fixed clone of the scene behind it. On open the glass ramps from clear to full while the panel unfolds and the items stagger in. It runs on the SVG filter, no WebGL.

Glass QR

A scannable QR rendered by a WebGL shader. Tapping the centre triggers a refraction ripple, a colour sweep across the modules, and a spin. Hovering tilts it.