# liquidGL: refracting glass panes in the browser with a WebGPU shader

> liquidGL is a JavaScript library that turns any fixed or sticky positioned element into a glass pane that refracts the live page behind it, rendered with WebGPU and falling back to WebGL2, WebGL1 and finally a CSS backdrop-filter. Its own feature table marks real-time refraction of CSS animations as unsupported, and its licence is a custom one that the repository cannot classify, which is the detail to read before shipping it.

**naughtyduk/liquidGL** — liquidGL – Liquid Glass Library - WebGPU/WebGL

- Repository: https://github.com/naughtyduk/liquidGL
- Website: https://liquidgl.naughtyduk.com
- Stars: 1,047 · Forks: 61
- Language: JavaScript
- License: NOASSERTION
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/naughtyduk-liquidgl

## Refracting what is behind an element, not a texture

Glass in a user interface is easy to fake and hard to do properly. The usual approach is a semi-transparent panel with a blur, which is what the CSS backdrop-filter property does. That is a blur of the backdrop, not a refraction of it. A real glass pane bends what you see through it, magnifying and distorting the content behind, and that is the effect Apple's Liquid Glass design language is named after and the effect this library is trying to reproduce on the web.

liquidGL turns any fixed or sticky-positioned element into what the README calls a perfectly refracted, glossy glass pane rendered in WebGPU, with an automatic fallback to WebGL. The description positions it as a recreation of Apple's aesthetic in the browser using an ultra-light WebGPU or WebGL shader, and it turns any DOM element into a refracting glass pane.

The hard part is the input side, and the README names it directly. WebGL has security limitations on reading live screen pixels, so a shader cannot simply sample the page behind the element. The library works around that with what it calls an innovative offscreen rendering technique, which is what allows it to refract dynamic content such as videos and text animations in real time.

That workaround is the whole engineering story of the project. The alternative, which every other glass effect on the web uses, is a snapshot of the backdrop or a blurred version of it, and a snapshot cannot follow a video underneath it. A lens that only bends a frozen image is a filter, not a lens, and the difference is visible the moment something moves.

The scope is narrow in one specific way, and the README states it in the first sentence: fixed or sticky-positioned elements. An element in the normal flow of a document scrolls away, and the library is not aimed at that case. The effect is aimed at the pieces of an interface that stay put, toolbars, sidebars, floating panels, and the kind of chrome that sits on top of scrolling content.

## The fallback chain and where you can start it

Rendering the page behind an element requires capabilities that browsers still arrive at in different order, so the library is built around a chain of four backends. WebGPU first, then WebGL2, then WebGL1, and finally the CSS backdrop-filter. The README is explicit that nothing has to be configured, that the chain is fully automatic, and that the same options are honoured by every backend including the CSS one.

The option values are 'auto', 'webgpu', 'webgl2' and 'webgl'. Automatic selection is convenient and it is also a problem for testing, because a machine with WebGPU will never exercise the WebGL2 path unless you stop it. That is what the engine option is for. Its values are 'auto', 'webgpu', 'webgl2' and 'webgl', and choosing one forces the chain to start there rather than at the top:

There is also a URL parameter for a quick check without changing code, which appends the engine choice to the page URL:

```text
?liquidGL-engine=webgl2
```

The URL parameter is the faster of the two for a quick check, since it needs no rebuild of the page, and the option is the one to keep in the source when you know which path you want to support.

The interesting claim in the chain is the last step. When no GPU path is available, the library falls back to CSS backdrop-filter, which is the plain blurred translucent panel that every browser can do. A page that looks like frosted glass on WebGL2 and like a flat blur on an old machine is a reasonable degradation, but it is not the same image, and any visual design that depends on the refraction will look different across the chain. The feature table marks the fallback as supported, which is true in the sense that it renders, and misleading if read as pixel equivalence.

The dynamic shadows, specular highlights, adjustable bevel and interactive tilt in the table are all part of the same shader, so they degrade with it, and the CSS path is limited to what the property can express.

## The option surface: tint, aberration, fluid interaction and drag

The recent additions are all per-lens options, and they are worth reading as a set because they show the direction of the library.

Tinted glass uses the new tint option to dye the pane with any CSS colour, taking the colour's alpha channel as the dye strength. A dark colour with low alpha gives a dark glass look, a saturated violet gives a coloured one, and the specular highlights stay clean white on top of the dye rather than picking up the tint. It is adjustable at runtime through lens.setTint, and it is honoured by every backend, WebGPU, WebGL and the CSS fallback, which is the kind of consistency that is easy to claim and worth checking.

The option value looks like this:

```js
tint: "rgba(8, 10, 20, 0.25)"
```

Chromatic aberration is the opposite of tint in spirit. The aberration option disperses the red and blue channels to either side of the refraction vector, with blue displaced further than red, matching the way real glass disperses shorter wavelengths more strongly. The dispersion scales with the refraction offset, so it concentrates at the bevelled edge and vanishes across the flat centre, which is the physically sensible place for it and the reason it does not fog the middle of the pane. It defaults to 0, which is off.

Fluid interaction is the most ambitious of the three. Setting interaction to fluid gives a viscous, touch-aware surface that displaces and follows the cursor or finger in real time, and the effect is tuned with interactionStrength, interactionRadius and interactionViscosity. The viscosity parameter is the interesting one, because it is what separates a surface that follows a pointer from one that lags behind it like a heavy liquid.

```js
interaction: "fluid"
```

The interaction model also has a direct manipulation form. A lens with draggable set to true can be picked up and moved with mouse or touch, firing on.dragstart, on.drag and on.dragend callbacks, and the property can be toggled at runtime with lens.setDraggable. There is a helper GUI for exploring the options, and a long list of demo pages on the project site covering an audio player, a menu bar, an AI search field, multiple lenses, chromatic aberration, fluid interaction, frost and specular, tinted glass, stacked lenses and true refraction.

## Several lenses, one canvas, and how to take one away

Multiple lenses means any number of lenses can share a page, each with its own options, and all of them are drawn through a single shared canvas for performance. That single-canvas decision is what keeps the cost of the second lens from being the cost of a second context and a second copy of the page, and it is the reason the library can claim to stay light as the number of lenses grows.

Stacked lenses is the harder half. Lenses can render on top of one another's refracted output, so a lens's glass can itself be refracted by another lens above it. The stacking order is controlled with the zIndex option, which gives explicit control over a lens's position in the stack and is used to order stacked and multiple lenses together with their shadow and tilt layers. The default is the element's own effective z-index, which is the sensible choice, since in most interfaces the DOM order and the visual order are the same thing.

The cleanup story is also handled per lens. Each instance exposes lens.destroy, which tears down that one lens, its styles, its listeners and its GPU resources, without affecting the other lenses sharing the same canvas. That matters more than it sounds. A page that creates and removes a lens on every route change, or on every open of a panel, leaks GPU resources and DOM listeners in a shared-canvas architecture unless the teardown is per instance, and the library provides exactly that.

Two other behaviours in the feature table are worth knowing about. Auto-resize handling means a lens keeps working when its element changes size, and auto video refraction means a video element that plays behind a pane is refracted live rather than as a frozen frame, which is the case the offscreen rendering technique exists for.

## Where the effect breaks down

The project's own feature table contains the limitation that matters most, and it is the only row marked as not supported. Real-time refraction of CSS animations is unsupported, while real-time refraction of static content, of video and of text animations is listed as supported. The distinction is that CSS animations, as opposed to script-driven text animation, move the backdrop in a way the offscreen capture does not follow, so what you see refracted goes stale while the animation plays.

For a designer this is a real constraint. A glass panel sitting over a page with a CSS-animated background will look correct at rest and wrong in motion, and the wrongness is subtle enough that it is likely to be blamed on the library rather than on the animation technique. The fix is to animate with a technology the capture follows, which is exactly what the script-driven text animation support implies, and it is a constraint on the surrounding page rather than on the lens.

The second limitation is the WebGL security boundary the library works around rather than removes. The offscreen rendering technique is a workaround for not being able to read live screen pixels directly from a shader, and a workaround has a cost: the page content has to be rendered again to be sampled, so the work is duplicated. The README describes the library as lightweight and performant in its feature table and as ultra-light in the overview, but it publishes no measurement, so a team with a performance budget should measure the frame cost of a lens on the hardware their users have.

The third is browser and hardware reach. WebGPU is recent enough that the fallback is a normal part of the experience, and a page that renders differently on a machine with WebGPU and one without is a design problem as much as a technical one. Combined with the position restriction to fixed and sticky elements, the practical envelope is a set of interface chrome elements over content, not a general-purpose glass treatment for arbitrary layout.

## Installing from npm or loading the browser script

The library is published to npm as liquid-gl, and the README states plainly that the package directory contains the npm package source and is not required when using the CDN or the browser script. The install is a single command:

```bash
npm install liquid-gl
```

The version marker in the README is 3.0.0, which corresponds to the note that liquidGL is now available on npm.

The two delivery routes matter for different reasons. The npm package is the choice for a build system that bundles assets, where the shader source and the module can be processed by the toolchain. The CDN or browser script route is the choice for a quick prototype, a landing page or any environment where adding a bundler is more work than the effect is worth. Since the project is a single-page effect with a helper GUI and a set of demos, the browser route is unusually easy to evaluate.

The repository is small, with an assets directory for images, a demos directory holding the example pages, an index.html that appears to be the project page, a package directory for the npm sources, a scripts directory, and the README and LICENSE at the root. The demos on the project site are the fastest way to judge the effect, and the multiple lenses and stacked lenses pages in particular show whether the shared canvas approach holds up when the page is busy.

For a team evaluating this, the sensible order is to open the demos, force the engine down the chain with the URL parameter on each browser you care about, and only then look at the source. The visual result varies along the chain, and no amount of reading the options list will tell you which backend a given user gets.

## A custom licence, a demo site and an open source support link

The licence is the thing to read carefully. The repository is recorded as using a custom licence that GitHub cannot classify, with the LICENSE file as the governing document, and the README carries a link to support open source development through a payment page. The distinction between an open source licence and a source-available one with a custom terms file is not academic for a library you intend to ship inside a product, so the LICENSE file is the first document to open, not the last.

The rest of the metadata is favourable. The repository is not archived, and the last push was on 2026-10-01, so the project is being worked on. There are no GitHub releases attached, which means versions are tracked in the npm package and in the README's version marker rather than in tags, and a consumer has to decide whether to pin a version there. The project homepage is liquidgl.naughtyduk.com, the topics list covers apple, glass, glassmorphism, liquid, webgl, magnifying glass and interactive, and the language is JavaScript with no build-time toolchain visible in the repository, which is consistent with a library meant to be dropped into a page.

The shape of the project is a single author publishing a well-polished visual effect library with a demo site, a helper GUI and an npm package, and that is worth weighing against the fact that the effect depends on rendering internals that browsers keep changing. The WebGPU path is new enough that its API surface may shift, and the library's own approach of working around WebGL's pixel-reading limits means the implementation touches exactly the parts of the platform that are still moving.

The README's description of the effect as a recreation of Apple's design language is a description of the aesthetic being copied, not of any Apple code or asset being used.

## Conclusion

Adopt liquidGL when a product needs a polished glass effect over live content and can live with the fallback chain and the browser support that implies, which is the case for a design system demo or a marketing surface. It is the wrong tool for animating the refracted content itself, because real-time refraction of CSS animations is listed as unsupported, and it is the wrong tool for a static site that CSS backdrop-filter alone would solve. Before using it, read the LICENSE file, since the repository records a custom licence that GitHub cannot classify, and check the fallback behaviour in the browsers you support, because the WebGL path and the CSS path do not produce the same image.

## FAQ

### What does liquidGL do in the browser?

It turns any fixed or sticky positioned DOM element into a glass pane that refracts the live content behind it, rendered with a WebGPU shader and falling back to WebGL2, WebGL1 and CSS backdrop-filter. It uses an offscreen rendering technique to get around WebGL's restriction on reading live screen pixels.

### How do I install liquidGL and which rendering engine will it use?

Run npm install liquid-gl, or load the browser script from a CDN. The backend chain is automatic, WebGPU then WebGL2 then WebGL1 then CSS backdrop-filter, and you can start the chain at a specific point with the engine option or the ?liquidGL-engine=webgl2 URL parameter.

### What is not supported by liquidGL?

Real-time refraction of CSS animations. The feature table marks it as unsupported while static content, video and text animations are listed as supported, so a pane over a CSS-animated backdrop will not track the motion.

### Can liquidGL show several glass panes on one page?

Yes. Any number of lenses can share a page with their own options, all drawn through a single shared canvas, and lenses can be stacked so one lens's glass is refracted by another. Stacking order is controlled with the zIndex option, and lens.destroy removes a single lens without affecting the others.

### What licence does liquidGL use?

The repository is recorded as using a custom licence that GitHub cannot classify, with the LICENSE file as the governing document rather than a standard open source licence. The README also links to a page for supporting the open source development.

## Sources

- [Issues](https://github.com/naughtyduk/liquidGL/issues)
- [naughtyduk/liquidGL on GitHub](https://github.com/naughtyduk/liquidGL)
- [Project website](https://liquidgl.naughtyduk.com)
- [README](https://github.com/naughtyduk/liquidGL/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/naughtyduk-liquidgl
