← Open the editor

Liquid Glass

A WebGL2 recreation of Figma's Glass effect. It bends, blurs and colour-splits whatever sits behind a shape, then gives you the exact settings and a ready-made brief for rebuilding the same look on your own website.

What this effect actually is

Figma's Glass is a 2D screen-space effect. There is no 3D geometry, no volume, no light transport and no ray tracing. Everything you see is produced by displacing and colour-splitting the pixels rendered behind the shape.

The most useful consequence: on a flat background the effect is invisible. Refracting a uniform colour returns that same colour, and splitting it into red, green and blue samples averages straight back to it. The effect only exists where there is detail behind it. If you are testing and seeing nothing, that is why.

Using the editor

Pick a shape, or draw one

Use the toolbar under the canvas: choose a shape, then drag on the image to draw it. While dragging it stays a flat silhouette so the outline is easy to judge; the glass is applied when you release. Press V for the move tool, drag inside the shape to reposition, drag a handle to resize, hold Shift to preserve the ratio.

Shortcuts: V move, R rectangle, O/E ellipse, L pill, T triangle, P pentagon, S star. Ctrl+Z and Ctrl+Shift+Z undo and redo everything, including shape edits.

Bring your own artwork

Background swaps the photo behind the glass — you can also drop an image onto the canvas or paste one with Ctrl+V. Paste shape accepts SVG from Figma: right-click your vector, Copy as SVG, paste it in, and the glass adopts that outline.

Presets

Seven starting points in the left rail: Figma default, Frosted card, Prism, Water droplet, Subtle UI glass, Reading lens, Smoked glass. They change the glass and solver settings only — your shape, size and background are untouched, so you can audition looks without losing work.

Settings reference

SettingWhat it does
RefractionHow far the glass displaces the backdrop. The main bend.
DepthWidth of the rounded rim inward from the outline.
DispersionRainbow fringing. A rim effect — never appears in the centre.
FrostIsotropic blur of the backdrop. Should add no colour at all.
SplaySpread of the projected light lobe.
LightAngle and intensity of the specular pass. 0% disables it.

Solver internals are the shader's own units: rim falloff, normal gain, chroma restore, spectral spread, height-field smoothing. Rim falloff, chroma and spectral spread are the three that decide whether the result reads as real glass.

The algorithm

Reproduce these stages in order.

  1. Rasterise the shape path into a coverage mask.
  2. Build a height field: a Euclidean distance transform of the mask, mapped to a rounded rim profile — t = min(dist / depth, 1), then height = sqrt(1 - (1-t)^2). Blur it slightly or the shape's medial axis creases visibly.
  3. Take the surface normal from a 3×3 Sobel gradient of that field, and soft-limit the slope so thin features cannot spike: scale = lim * tanh(gm/lim) / gm with lim ≈ 1.35. Use tanh rather than clamp — it is smooth, so no new hard edge appears where the limit engages.
  4. Refraction: offset the backdrop lookup by normal.xy * refraction.
  5. Frost and dispersion as two separate accumulations (see below), blended by the rim mask.
  6. Restore chroma: spectral averaging pulls toward grey, so keep the integrated luminance and scale only the colour difference from it.
  7. Composite over the backdrop using the coverage mask.

The mistake worth avoiding

Do not compute frost and dispersion in one sampling loop. If the same index drives both the blur ring and the wavelength weight, every frost sample is tinted, so raising frost paints concentric rainbow rings across the interior instead of softening it. They are independent operations:

// frost: untinted, isotropic, pre-filtered
for (i) {
  angle  = i * 2.39996;              // golden angle: no spokes
  offset = vec2(cos, sin) * radius * sqrt(t);
  blur  += textureLod(bg, p + d + offset, lod);
}

// dispersion: spectral, along the gradient, no frost offset
for (i) {
  wt   = spectralWeight(t);          // gaussians at 0.0 / 0.5 / 1.0
  num += wt * texture(bg, p + d + dir * (w * (t*2-1)));
}

col = mix(blur / n, num / den, rimMask);

Two details make a sparse blur look smooth: golden-angle sample placement, so taps never line up into spokes, and reading from a mipmap level matched to the tap spacing, so each tap already carries the average of the gap to its neighbours. Without the mip, point samples leave visible ghosts no matter how you arrange them. Never use per-pixel dithering to hide this — it trades banding for visible grain.

One more: a wide dispersion band needs a high sample count, 24 or more. Band width and tap count must move together, or the spectral integral quantises into hard steps.

Putting it on your own site

In the editor, click Copy prompt in the left rail. You get a complete brief — the pipeline above, every current setting, the solver internals as JSON, and your shape's SVG path and viewBox. Paste it into any AI coding assistant and you get a working component back. Save / load settings in the header gives you the same numbers as plain JSON to keep or share.

The one real constraint: this effect needs a backdrop texture. Browsers cannot read live page pixels from a shader, so you must supply the backdrop yourself — an image, a video frame, or a rasterised snapshot of the DOM behind the element. Plan for that before committing to glass over arbitrary scrolling content.

Performance notes