Apple Liquid Glass for the web

apple-liquid-glass-webgl is a zero-dependency WebGL2 library that gives any HTML element Apple-style Liquid Glass. Point it at an element and it refracts what is actually behind that element on the page, while it scrolls, resizes and animates — in Chrome, Safari and Firefox alike, because the refraction runs in a shader rather than in the Chromium-only backdrop-filter: url(#filter) trick.

npm install apple-liquid-glass-webgl

The short answer. Two lines give you the effect. import { LiquidGlass } from 'apple-liquid-glass-webgl', then new LiquidGlass('.navbar'). The element keeps its content, layout and events, the glass is drawn behind its children, and the shape and corner radius come from the element's CSS border-radius. It works with React, Vue, Svelte, Next.js or plain HTML, and where WebGL2 is missing it falls back to backdrop-filter.

Why it survives Safari and Firefox. Most Liquid Glass libraries drive an SVG displacement map through backdrop-filter: url(#filter). Only Chromium honours that: Safari and Firefox accept the property, silently drop the SVG part and leave you a flat blur. This library never uses it. It repaints the page behind the element into a texture and bends that texture in a WebGL2 fragment shader — and WebGL2 has shipped in Chrome, Edge, Firefox and Safari 15+. You also keep your DOM: the glass is a canvas behind the element's children, so the text is still real text — selectable, translatable and read by screen readers — which is the usual trade-off people accept when they reach for WebGL.

Four liquid-glass surfaces refracting a liquid marble backdrop, then the Earth, with the pre-blur ramped up behind them
Four glass surfaces over live backdrops. Everything here is the public API; the playground runs the same code.
On this page

What apple-liquid-glass-webgl is

apple-liquid-glass-webgl is an open-source (MIT) JavaScript package that renders Apple's Liquid Glass material on the web with WebGL2. Browsers do not let WebGL read page pixels, so the library repaints what is behind the glass itself into a texture, then runs a signed-distance-field refraction shader over it. The result is a surface that bends the page behind it, gathers the scene at its edges, splits the colour channels slightly and carries a rim light, a specular highlight and a contour hairline — the optical behaviour of Apple's material, not a blurred rectangle.

It works on ordinary DOM elements (a navbar, a card, a toast, a sidebar), on groups of elements drawn on one canvas, and on canvases you own in a game or WebGL scene. It has no runtime dependencies, ships as plain ES modules with TypeScript declarations, and needs no build step or bundler configuration.

Which approach should you use: CSS, SVG displacement, or WebGL?

There are three ways to put Liquid Glass on a web page, and they fail in different places. Plain CSS backdrop-filter can blur and tint what is behind an element, but it cannot bend it. An SVG displacement map driven through backdrop-filter: url(#filter) does bend it, but only in Chromium — Safari and Firefox drop the filter and render a flat blur. A WebGL shader bends it everywhere WebGL2 runs, which is every current browser, at the cost of repainting the backdrop yourself. This library is the third kind.

CSS backdrop-filter SVG displacement map apple-liquid-glass-webgl
Blur and tintYesYesYes (frost, tint, tintTone)
Refraction through the body of the glassNoYesYes (refraction)
Refraction in Safari and Firefoxn/aNo — flat blurYes — WebGL2 ships in both
Edge capture: the scene compressed at the borderNoLimited by the ±127 px displacement rangeYes (edgeReach, edgeWidth)
Chromatic dispersionNoApproximated per channelYes (dispersion)
Rim light, backdrop reflection, specular highlightFaked with gradientsFaked with gradientsComputed from the backdrop itself
Press deformation for controlsNoNoYes (pressure)
Your content stays real DOMYesYesYes — the glass is drawn behind the element's children
CostCompositorFilter graph per frameOne WebGL2 context, redrawn only on change

The approaches are not exclusive: backdrop-filter is this library's built-in fallback, so a page degrades to conventional glassmorphism where WebGL2 is missing.

The usual objection to WebGL glass — that you give up semantic HTML and accessibility — does not apply here. Nothing is redrawn into a canvas except the backdrop. Your element's own children are never touched, so text stays selectable and screen-reader-readable, links stay focusable, and your event handlers keep working.

Quick start

npm install apple-liquid-glass-webgl
import { LiquidGlass } from 'apple-liquid-glass-webgl';

new LiquidGlass('.navbar');

That is the whole integration. Readable text over a busy photo wants a body, and tint ramps in gently, so readable values sit around 0.5–0.8:

new LiquidGlass('.panel', { frost: 0.45, tint: 0.5, tintTone: 'light' }); // dark text
new LiquidGlass('.toast', { frost: 0.3,  tint: 0.7, tintTone: 'dark'  }); // white text

For a grid, draw the whole group on one canvas instead of one context per card:

new LiquidGlass('.card-grid', { targets: '.card', frost: 0.3, tint: 0.2 });

Lifecycle:

const glass = new LiquidGlass('.card', options);
await glass.ready;              // first frame drawn, images loaded
glass.update({ tint: 0.4 });    // merge options
glass.refresh();                // re-read selectors and repaint
glass.destroy();                // restore the element, free the WebGL context

LiquidGlass.isSupported();      // WebGL2 available
LiquidGlass.from('.card');      // the instance on an element
LiquidGlass.refreshAll();       // repaint everything next frame

What is behind the glass

By default you do not supply a background at all. backdrop: 'auto' paints the page below the element in CSS paint order, z-index included: backgrounds, borders, <img>, <video>, <canvas> and text. A heading that scrolls under a fixed navbar shows up inside the glass, aligned with the rest of it.

backdropUse it for
'auto'the page below the element (default)
'#hero-video' or an elementa specific image, video, canvas or CSS background, where it sits on the page
'/wallpaper.jpg'an image covering the viewport, like a fixed wallpaper
{ source, fit, position, anchor }a source fitted like object-fit into the viewport, document or an element
(ctx, region) => { … }anything else: draw it yourself in page coordinates
[ … ]several of the above, bottom to top

Everything is registered in page coordinates, so the part of the photo under a card is the part of the photo under that card. Cross-origin images need CORS headers. auto does not reproduce ::before/::after, shadows, CSS filters, SVG or form controls — add those with a painter function appended after 'auto'.

Options

new LiquidGlass(element, {
  tint: 0.2,          // milky layer, 0–1.5 (default 0: clear)
  tintTone: 'dark',   // 'light' (default), 'dark', or 'auto' from the backdrop
  frost: 0.3,         // blur behind the lens, as a ratio of the short side (default 0)
  backdrop: 'auto',
  material: { refraction: 70 },
});
OptionDefaultMeaning
tint, tintTone, frost, opacity0, 'light', 0, 1The look. Use tintTone: 'dark' behind white text.
backdrop'auto'What the glass refracts.
materialpackage defaultThe optical parameters; unknown keys throw.
targets—Draw several descendants as glass on one canvas.
shape, radiusfrom CSS'rect', 'pill' or 'circle', CSS px.
live'auto'Redraw every frame while a canvas or playing video sits behind the glass.
fallback'css''css', 'none', or your own function.
maxDpr2Resolution cap.
zIndex-1Glass layer inside the element: behind its content.
respectReducedTransparencytrueNear-opaque glass under prefers-reduced-transparency.

The material itself is tunable: refraction, edgeReach, edgeWidth, dispersion, frost, backdropBlur, body, absorption, tint, rim, reflection, highlight, lightAngle, echo, hairline, hairWidth and roundness. Ratios are relative to each surface's short side, so one material works from a 40 px toggle to a 400 px card. The playground edits every value live and copies the result as code.

React, Next.js, Vue and Svelte

You do not need a React wrapper package — this is the whole React component, and the same eight lines work in Next.js, Remix and Astro. Mount after the element exists, destroy on unmount. It is safe under StrictMode's double mount, and importing the package is safe during server rendering because it only touches the DOM when constructed.

// React — a complete, reusable glass component
import { useEffect, useRef } from 'react';
import { LiquidGlass } from 'apple-liquid-glass-webgl';

export function Glass({ options, children, ...props }) {
  const ref = useRef(null);
  useEffect(() => {
    const glass = new LiquidGlass(ref.current, options);
    return () => glass.destroy();
  }, []);
  return <div ref={ref} {...props}>{children}</div>;
}

// <Glass className="navbar" options={{ frost: 0.3, tint: 0.2 }}>…</Glass>

In the Next.js App Router, put 'use client' at the top of that file. Nothing else changes: the component renders a plain <div> on the server and the glass attaches on hydration.

// Vue
onMounted(() => { glass = new LiquidGlass(el.value, { tint: 0.2 }); });
onBeforeUnmount(() => glass.destroy());

// Svelte action: <nav use:liquidGlass={{ tint: 0.2 }}>
export const liquidGlass = (node, options) => {
  const glass = new LiquidGlass(node, options);
  return { update: (next) => glass.update(next), destroy: () => glass.destroy() };
};

Ready-made navbar and switch

The package ships two controls with the pressed liquid lens, drag, springs, keyboard support and ARIA. Size them with CSS; like LiquidGlass, they read the page behind them.

import { LiquidGlassNavbar, LiquidGlassSwitch } from 'apple-liquid-glass-webgl';

const nav = new LiquidGlassNavbar('#nav', {
  items: [
    { value: 'home', label: 'Home' },
    { value: 'work', label: 'Work', icon: '▣' },
    { value: 'read', label: 'Read' },
  ],
  value: 'home',
  onChange: (value, index) => router.push(value),
});

const toggle = new LiquidGlassSwitch('#dark-mode', {
  checked: false,
  onChange: (checked) => document.documentElement.classList.toggle('dark', checked),
});
Press effects: a selected capsule dragged along its track, a button held until it blooms, a toggle flipped
Pressing blooms the knob in place; dragging it past the midpoint changes state. Arrow keys, Home/End and Space keep both controls keyboard-accessible.

Canvas and WebGL scenes

LiquidGlassWebGL is the renderer underneath. Use it when you own the canvas and compose the frame yourself — canvas apps, games, three.js or Pixi scenes. Coordinates are canvas CSS pixels and the backdrop is whatever you hand it.

import { LiquidGlassWebGL } from 'apple-liquid-glass-webgl';

const glass = new LiquidGlassWebGL(canvas, { compositeMode: 'overlay' });
glass.setBackdrop(sceneCanvas);      // canvas/video: live, image: static
glass.setElements([
  { id: 'bar',  shape: 'pill',   x: 100, y: 28,  width: 560, height: 66, tint: 0.2 },
  { id: 'lens', shape: 'circle', x: 300, y: 200, size: 120, pressure: 0.6 },
]);
glass.render();

A WebGL canvas is cleared after each frame, so create it with preserveDrawingBuffer: true or call LiquidGlass.refreshAll() right after you render. Hit testing (hitTestEvent, hitTest, distanceAt) uses the shader's own geometry, so circles, capsules and squircle corners are respected instead of their bounding boxes.

Browser support, performance and limits

FAQ

How do I add an Apple Liquid Glass effect to a website?

Install apple-liquid-glass-webgl from npm and point it at an element: new LiquidGlass('.navbar'). The element keeps its content, layout and events, the glass is drawn behind its children, and the shape comes from the element's CSS border-radius. Nothing else is required — no wallpaper, no canvas, no build step.

Does the Liquid Glass effect work in Safari and Firefox?

Yes, with the refraction intact. This is the usual failure point elsewhere: most Liquid Glass libraries drive an SVG displacement map through backdrop-filter: url(#filter), which only Chromium honours — Safari and Firefox accept the property, silently drop the filter and leave a flat blur. This library bends the backdrop in a WebGL2 fragment shader instead, and WebGL2 has shipped in Chrome, Edge, Firefox and Safari 15 and later, on desktop and mobile. Where WebGL2 is genuinely unavailable it falls back to backdrop-filter on purpose.

Do I lose my DOM, accessibility or text selection, like with other WebGL glass?

No, and this is the trade-off people usually accept when they choose a WebGL library. Only the backdrop is repainted into a canvas. Your element's own children are never redrawn: text stays real text that can be selected, translated and read by a screen reader, links stay focusable, and your event handlers, layout and CSS are untouched. The glass sits behind the children at z-index: -1.

How does it compare to SVG displacement / liquid-glass-react style libraries?

They are the same idea reached by a different route. An SVG displacement map is simpler and needs no WebGL, but it only refracts in Chromium, is capped by the ±127 px range of an 8-bit displacement channel, and fakes rim light and dispersion with gradients. A WebGL2 shader refracts in every browser that has WebGL2, computes the rim light, reflection and dispersion from the actual backdrop, and can deform under a press. The cost is that the page behind the glass has to be repainted — which this library does for you, in page coordinates, with backdrop: 'auto'.

Can I use it in Next.js, or without React at all?

Both. It is a framework-free class, so plain HTML and a <script type="module"> are enough. In the Next.js App Router, add 'use client' to the component file that constructs it; the eight-line React component in React, Next.js, Vue and Svelte is the whole integration, and no React-specific wrapper package is needed.

How is this different from CSS backdrop-filter glassmorphism?

backdrop-filter can only blur and tint the pixels behind an element. Apple's Liquid Glass also bends light: the backdrop is displaced by the shape of the glass, the edges gather and compress the scene, and the colour channels separate slightly. This library repaints the page behind the element and runs a signed-distance-field refraction shader over it in WebGL2, adding edge capture, dispersion, rim light, backdrop reflection, a specular highlight and a contour hairline that CSS cannot express.

Does it work in React, Vue or Svelte?

Yes. It is framework-free: construct it after the element exists and destroy it on unmount. In React that is a useEffect with a cleanup calling glass.destroy() — safe under StrictMode's double mount. Vue uses onMounted/onBeforeUnmount, Svelte a use-action. Importing during server rendering is safe.

Do I have to supply a background image?

No. The default backdrop: 'auto' paints the real page behind the element, in CSS paint order, and keeps it aligned in page coordinates while you scroll. You pass a backdrop explicitly only when you want something else, such as a fixed wallpaper, one specific <video>, or a painter function you write.

What happens without WebGL2?

The element falls back to a CSS backdrop-filter surface. Every glass element carries data-liquid-glass="webgl" or "fallback" so one CSS rule can style both, and LiquidGlass.isSupported() reports the same thing in advance.

Is it expensive? How many glass elements can one page have?

Browsers allow roughly 16 WebGL contexts per page; one element or switch uses one, a navbar uses two. For a grid of cards use targets so the whole group shares a single canvas. Frames are skipped when nothing changed, and live redraw turns itself on only while a canvas or playing video sits behind the glass.

Is it free and open source?

Yes — MIT licensed, no runtime dependencies, plain ES modules with TypeScript declarations, no build step. Source on GitHub, published on npm.

Can I use it on a canvas, game or three.js scene?

Yes. LiquidGlassWebGL takes your canvas, a backdrop source and a list of glass shapes, and compositeMode: 'overlay' leaves the rest of the canvas transparent so the glass composites over your own rendering.

Does it include ready-made Liquid Glass components?

LiquidGlassNavbar (a segmented navigation bar) and LiquidGlassSwitch (a toggle), both with the pressed liquid lens, drag, springs, keyboard support and ARIA.

Does it reproduce the iOS 26 Liquid Glass look?

It reproduces the optical behaviour — body refraction, edge capture, dispersion, rim light, backdrop reflection, specular highlight and contour hairline — as a material you tune rather than a single preset. The playground exposes every value as a live slider and copies the material as code, so you can match a specific Apple surface.

Package facts

Packageapple-liquid-glass-webgl on npm
Version2.6.0
LicenseMIT
Runtime dependenciesNone
FormatES modules with TypeScript declarations; subpath exports /v2, /dom, /controls
RequiresA browser with WebGL2; Node 18+ for the tooling
Main exportsLiquidGlass, LiquidGlassWebGL, LiquidGlassNavbar, LiquidGlassSwitch, getDefaultMaterial
Repositorygithub.com/Oliverrr2424/webgl-apple-liquid-glass
Playgroundoliverrr2424.github.io/webgl-apple-liquid-glass
Machine-readable summary/llms.txt, /llms-full.txt