
The liquid glass button
Buttons, WebGL, Hover, Physics
NewFree
The bottom bar of an app, Home, Search, Library and Profile, in frosted glass over a strip of abstract album covers drifting slowly behind it. The chosen tab sits under a lens of clear liquid glass: changing tab, the lens glides on a spring and stretches with its speed into a drop, round at the front and thin at the back, then settles with a wobble; all the way it really refracts what is under it, magnifying the icon and bending the covers, with the rainbow of chromatic aberration on its rim. It can be dragged with a finger too: held it swells, let go it picks the nearest tab.
A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React and Vue tabs hold the same demo as a ready component, which switches its animations off when it leaves the page.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The liquid glass tab bar</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/figtree@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the tab bar of an app, in frosted glass, over a strip of album covers that drifts slowly behind it. The tab
you are on sits under a lens of clear liquid glass: going to another tab, the lens glides there, stretching
into a drop as it speeds up, magnifying and bending the icons and the covers under it. It can be dragged
with a finger and let go: it picks the nearest tab. Covers, bar, icons and lens are all drawn in one WebGL
canvas; the links on top are the real, clear, navigation. The icons below are read by the script -->
<div class="tabbar" data-tabbar>
<canvas class="tabbar__scene" aria-hidden="true" data-canvas></canvas>
<nav class="tabbar__nav" aria-label="Main" data-nav>
<a class="tabbar__tab" href="#home" data-tab data-autoclic>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 10.5 12 4l8 6.5V19a1 1 0 0 1-1 1h-4.5v-5.5h-5V20H5a1 1 0 0 1-1-1z"/></svg>
<span>Home</span>
</a>
<a class="tabbar__tab" href="#search" data-tab data-autoclic>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M10.5 4.5a6 6 0 1 0 0 12a6 6 0 1 0 0-12zM15 15l4.5 4.5"/></svg>
<span>Search</span>
</a>
<a class="tabbar__tab" href="#library" aria-current="page" data-tab data-autoclic>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 4.5v15M9.5 4.5v15M13.6 5.3l4.8 14.2"/></svg>
<span>Library</span>
</a>
<a class="tabbar__tab" href="#profile" data-tab data-autoclic>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 12a4 4 0 1 0 0-8a4 4 0 0 0 0 8zM4.5 20c.9-3.7 3.8-5.5 7.5-5.5s6.6 1.8 7.5 5.5"/></svg>
<span>Profile</span>
</a>
</nav>
<p class="sr" aria-live="polite" data-live></p>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
<script src="script.js"></script>
</body>
</html>Change values, colours and text with the controls and watch the animation respond live. Or write in the code yourself. Then download the folder with your changes.
18 values to change
With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:
Take “The liquid glass tab bar” from Animorio and fit it into my siteFirst pass, into a texture the size of the canvas: the covers (a strip painted once in a 2D canvas and read with fract, so it drifts forever), the bar as a signed distance rounded rectangle, its shadow, the frosted glass and the icons. Second pass, on the screen: that texture, bent inside the lens. So the lens bends the icons and the frosted glass too, not just the background.
The bar's glass blurs nothing in real time: it reads a second copy of the strip, blurred once by the canvas (filter blur, repeated at the sides so the blur wraps round the ends too; where filter is missing, a copy at an eighth of the size softened by the linear filter), five times round the point, and darkens it with the glass colour. A thread of light follows the edge, stronger at the top.
The lens is an uneven capsule (two circles of different radii and the lines touching both, from Inigo Quilez's formula), turned the way it is going. The length between the two centres and the two radii come from springs: still it is a pill; fast it stretches, the front radius shrinks a little and the back one more, so it becomes a drop. Near the ends of the bar it stretches less, so it never pokes out.
The normal comes from the field itself, with four nearby reads. Inside the lens a domed profile (flat in the middle, steeper and steeper towards the rim, like a quarter circle) says how far the glass reaches out for what lies beyond the rim, along the normal; the middle magnifies by ZOOM. Red, green and blue are read at three different distances (PRISM): at the rim the colours split. On top, a thread of light on the side of the light, a weaker one on the other, a soft gloss and a dark ring just outside.
The lens's position is a spring towards the chosen tab (SPRING), integrated in steps of 1/240 of a second with the frame's step capped at 1/30. The stretch chases the speed on a second, livelier spring, so on arrival the drop shortens past its shape and wobbles. Dragging, the position spring stiffens onto the finger and a third spring lifts and swells the lens; let go, it picks the nearest tab. The icons take the chosen colour in proportion to how close the lens is.
| Name | File | Default | What it changes |
|---|---|---|---|
DRIFT | script.js | 16 | How fast the covers drift behind the bar, in pixels a second. |
SPRING | script.js | [300, 22] | The spring the lens glides on: stiffness, and damping (lower, more it overshoots). |
STRETCH | script.js | 0.55 | How much the lens stretches into a drop with its speed. |
BEND | script.js | 16 | How far the rim of the lens reaches out for what lies outside it, in pixels. |
BEVEL | script.js | 18 | How wide the rounded rim of the lens is, in pixels. |
ZOOM | script.js | 0.22 | How much the middle of the lens magnifies. |
PRISM | script.js | 0.28 | How far apart red and blue bend on the rim: the rainbow. |
For the bottom navigation bar of an app or a phone-first site, especially where colourful content scrolls under the bar: music, photos, video, a shop. From three to five tabs; the strip of covers is part of the demo and is replaced with the app's real content painted into the same texture.
The bar takes the width of the screen minus 32 pixels and the tabs share the room; the canvas draws at density 2. A tap on a tab sends the lens there, and the finger can drag it along the bar (touch-action: pan-y, so vertically the page still scrolls); the mouse hover that brightens a tab does not exist on a phone.
The covers stand still and the canvas is redrawn only when something changes. The lens does not travel or stretch: it fades out where it was and reappears on the new tab; dragged, it follows the finger without swelling or stretching.
GSAP 3.15.0, Figtree (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 11 KB; the libraries: GSAP 28 KB.
Tested on 5 October 2026
Use it in your own projects and in your clients’ projects. Do not resell it or redistribute it as is. GSAP stays under its own Standard license. Full license.
With your account’s email and password.
or with your email
You’re signed in as .
With your account, you can download the zip of every animation and every pack, for free.