Open source · MIT
Glass controls for React, drawn by one shader.
A component kit where every button, switch and slider is a refracting WebGL lens with spring physics, light and dark themes, and no runtime beyond three.js.
Every control, live
Each control is drawn by the WebGL glass shader: clear lenses that bend what's beneath at the rim, swell when pressed and settle back into flat pills. Press, drag and toggle; all of it is live.
Buttons
Clear glass; swells on press.
Segmented control
Hold and drag the frosted pill to turn it into a magnifying lens. The theme segments switch the page: sun, system, moon.
Toggles
Knobs lift into clear glass while held.
Sliders
Glass thumbs over the accent fill; press and drag to swell the lens.
How the glass works
Every control hands one lens to a shared WebGL shader. Drag the lens below and flip the render loop: both loops draw the identical image, but only one of them is free while nothing moves.
- shader passes / s
- 0
- main thread ms / s
- 0.0
Renders only while something moves
The springs are stepped every frame, but the shader runs only when a spring is still moving or a prop changed. At rest the control costs nothing.
One renderer for the page
A single WebGL2 renderer (three.js WebGPURenderer on its WebGL backend, shaders in TSL) draws every control and copies the result into each control’s own canvas, so a page full of glass never hits the browser’s context limit.
Two layers and one lens
A control paints two Canvas2D layers, what sits beneath the glass and what rides on top, and hands the shader one lens: position, size, bevel, refraction, frost.
Springs stepped per frame
Motion is damped springs; a view only renders while something moves or a prop changed. prefers-reduced-motion snaps every spring to its target.
Prerendered, hydrated lazily
Every page is prerendered to static HTML and the renderer loads on the client after hydration, so first paint is plain markup.
Run it, fork it, ship it
TanStack Start, Vite, Tailwind v4 and three.js. Clone it, run it with pnpm, copy the controls you need.
git clone https://github.com/bilalgursen/liquid-glass.git
cd liquid-glass
pnpm install
pnpm dev # Vite dev server
pnpm build # static prerender + verify-static-buildimport { Switch } from '#/components/ui/Switch'
import { RangeSlider } from '#/components/ui/RangeSlider'
<Switch label="Notifications" checked onChange={setOn} />
<RangeSlider label="Brightness" value={40} endIcon="sun" />
// Any subtree can pick its own theme:
<div data-theme="dark">…</div>Where things live
- src/components/ui/
- The kit: Button, Switch, Checkbox, RadioGroup, SegmentedControl, RangeSlider, Progress, TextField, Card, Icon.
- src/components/ui/glass/
- Shared glass engine: runtime.ts (WebGL renderer + TSL shader), physics.ts (springs, lens morph), draw.ts, tokens.ts.
- src/components/ui/LiquidGlass.tsx
- A bare glass pane you can put behind anything.
- src/components/liquid-glass-slider/
- The original hero slider the kit was ported from.
- src/components/site/
- The sections of this page and the shared site chrome.
- src/components/shadcn/
- shadcn/ui primitives used by the site chrome (button, badge, separator).