Snell refraction · SVG displacement · backdrop-filter

Glass that bends what sits behind it.

One ES module, no dependencies. Custom elements for switches, sliders and segmented controls, classes for buttons, fields, panels and menus, and data-glass for anything else. Chromium only.

Bench controls

Specimens

Components

Each snippet below is the exact markup of the specimen above it. Native inputs do the work underneath, so forms, labels and keyboard support behave as usual.

Buttons

Pill, round and tinted. They swell on press.

Toolbar

One glass capsule for a row of icon buttons. <hr> divides groups, and buttons with aria-pressed toggle when tapped.

S/N 0412-77 · calibrated 3 Oct 2026

Magnifier

Wrap anything: a zoom lens follows the pointer, or keyboard focus. Here it's a dock of icons and a line of fine print. On touch, slide a finger sideways.

Add to library

Tooltip

Hover or focus the button. interestfor opens a popover="hint" anchored above it, with no script in current Chrome.

Switch

The thumb turns into a lip-profile lens while pressed. Wraps a native checkbox with role="switch".

Slider

Press and drag: the thumb becomes a lens over the track. Wraps <input type="range">.

Stepper

Tap or hold either half. Wraps <input type="number">, so arrow keys and forms work and each half greys out at its limit.

Page dots

Swipe the slides, tap a dot, or drag across the dots to scrub. for points at any scroll-snap container and follows its pages.

Wheel picker

Scroll, swipe, drag or use the arrow keys; the glass band magnifies the row it holds. Wraps a native <select>, so forms and screen readers see a plain select.

Segmented control

Equal-width segments with a sliding glass indicator. Arrow keys move the selection and fire change.

Tab bar & pill nav

The same element with .lg-tabbar. Give it links instead of buttons for page navigation (aria-current), like the nav bar at the bottom of this page.

ColorChecker

  • Dark skin
  • Light skin
  • Blue sky
  • Foliage
  • Blue flower
  • Bluish green
  • Orange
  • Purplish blue
  • Moderate red
  • Purple

Collapsing nav bar

Scroll the list: the large title recedes, and the bar turns to glass with a soft blur along its edge. Pure CSS with scroll-driven animations; it follows its nearest scroller, or the page.

Floating sidebar

A segmented control with aria-orientation="vertical": the same lens, travelling down. Tap, drag or use the arrow keys; links instead of buttons make it page navigation.

Merging glass

One pane of glass cut to the outline of its children. Closer than --lg-merge (20px) they melt together; drag the slider to pull them apart.

Search field

A label wrapping a native input, so clicking anywhere on the glass focuses it.


Menu

The Popover API plus anchor positioning, with no positioning script. Picking an item closes it.

Right-click or long-press

Context menu

data-contextmenu opens an .lg-menu right where you clicked, kept on screen. From the keyboard (menu key or Shift+F10) it focuses the first item.

CALIBRATION · NOW Lens 2 is back in spec Wavefront error 0.04 λ RMS at 633 nm.

Panel

A rounded container for cards, sheets and notifications. Works on <dialog> too.

Toast

Slides in from the top and leaves after four seconds. Swipe it up to dismiss. Screen readers announce it.

import { toast } from './liquid-glass.min.js';

toast('Lens 2 is back in spec');
toast(node, { duration: 0 }); // stays until swiped away

Delete this scan?

The extraction and its report are removed from the case. This can't be undone.

Alert

A native <dialog>, opened with commandfor and no script. The buttons close it, and returnValue says which one was pressed.

Lens 2

Focal length
50 mm
Aperture
f/1.8
Wavefront error
0.04 λ RMS
Strehl ratio
0.94
MTF at 50 lp/mm
0.62
Distortion
0.3 %
Vignetting
0.8 EV
Elements
7 in 6 groups
Coating
MgF₂
Bench temperature
21.4 °C
Calibrated
3 Oct 2026

Sheet

Opens at half height. Drag it up to full height, or pull or flick it down to dismiss. It's a native modal <dialog>, so Escape and a tap outside close it too.

Any element

Any element

data-glass on anything with a border radius. Tune it per element with --lg-* properties, or call glass(el, opts).

Playground

Every option, live

Geometry changes (size, radius, bezel, thickness, profile, index, light, zoom) rebuild the maps. Refraction, blur, saturation and specular only change filter attributes, so they are cheap to animate.

Glass
profile
Generated map
Generated map · R = x shift, G = y shift, B = rim light

Reference

API

Load the module once with <script type="module" src="liquid-glass.js"></script>. It adds its styles in a cascade layer, so any unlayered CSS of yours wins.

Option Default Effect
--lg-profile squircle Bezel shape: squircle, circle, lip or concave
--lg-bezel auto Width of the curved edge in px (auto = 0.8 × radius)
--lg-thickness auto Glass height in px (auto = 1.2 × bezel). More = stronger bend
--lg-ior 1.5 Refractive index
--lg-refraction 1 Multiplier on the physical displacement
--lg-blur 1 Frost, as a Gaussian blur in px
--lg-saturation 5 Colour boost under the rim highlight
--lg-specular 0.7 Rim highlight opacity
--lg-light 225 Light angle in degrees (0 = from the right, 90 = from below)
--lg-zoom 1 Magnification of the flat centre
--lg-antialias 1 Smoothing of zoomed pixels, scaled to zoom and screen density. 0 turns it off, 2 doubles it
Piece Usage
glass(el, opts) Applies glass to any element and returns { filter, update(opts), destroy() }. Also available as el.glass.
[data-glass] Applied automatically, including to elements added later. Removing the attribute or the element cleans up. The glass is drawn on the element's ::before, so keep that pseudo-element free and avoid opacity below 1 on the element.
<lg-switch> Attributes pass through to the inner checkbox (name, checked, disabled…). Property .checked.
<lg-slider> min, max, step, value, name pass through. Property .value.
<lg-stepper> min, max, step, value, name pass through to a hidden <input type="number">. Hold to repeat. Property .value.
<lg-dots> for="id" follows a scroll-snap container's pages, or count="n". Tap, drag to scrub, arrow keys. Property .value, fires change.
<lg-segmented> Children are the segments. .value = selected index, fires change. Add .lg-tabbar for icon tabs, aria-orientation="vertical" for a sidebar.
<lg-picker> Wraps a <select>; its options are the rows. .value = selected value, the select fires change once the wheel settles.
.lg-navbar · .lg-large-title Sticky bar with three slots (leading, title, trailing), then a large title. Scrolling turns the bar to glass and swaps the titles; no script. Follows its nearest scroller.
<lg-merge> One glass pane cut to the smooth union of its children's boxes; children closer than --lg-merge px melt together. Follows their size and transitions; with drag you move the children, like the lenses on the bench.
<lg-magnifier> Wraps any content; a zoom lens follows the pointer and keyboard focus. Style lg-magnifier .lg-lens for its size and --lg-zoom.
.lg-btn Variants .lg-round and .lg-tinted
.lg-toolbar Icon buttons sharing one capsule; add role="toolbar" and an aria-label. <hr> divides groups; aria-pressed buttons toggle.
toast(content, opts) Banner from the top. content is a string or node; { duration } in ms (default 4000, 0 keeps it up). Swipe up to dismiss.
[data-contextmenu] Right-click or long-press inside opens the .lg-menu with that id at the pointer.
.lg-alert On a <dialog>. Open it with showModal() or command="show-modal"; buttons in a <form method="dialog"> close it with their value.
.lg-sheet On a <dialog> opened with showModal(). Detents: half and full height; height: auto fits the content. Give a long child overflow: auto to scroll it; closedby="any" closes on a tap outside.
.lg-tip On a popover="hint" element, opened by interestfor="id" on its trigger. Sits above it, flips below when there is no room.
.lg-field · .lg-panel · .lg-menu Search field, container, popover menu
--lg-accent · --lg-tint Theme colour and glass fill. Normal inherited custom properties.
Bench Components Playground API