
The liquid glass button
Buttons, WebGL, Hover, Physics
NewFree
The bottom bar of a thermal spa hotel's app made as a strip of pool water set in white stone. Under the water lie mosaic tiles with the net of light the sun draws on the bottom, and the sun falls on the chosen tab. The icons float. A tap drops a ripple where the finger lands: the rings run, bounce off the stone, bend the tiles, gather the light into bright lines and rock the icons, while the sun glides to the new tab. The mouse leaves a little wake. The water is a wave equation on a grid, drawn in WebGL.
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 pool tab bar</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/jost@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the tab bar of a thermal spa hotel's app, made as a strip of pool water set in white stone. Under the water
lie small mosaic tiles, with the net of light the sun throws on the bottom of a pool, and the sun falls on
the tab you are on. The icons float on the water. A tap drops a ripple where the finger lands and the sun
glides to the new tab; a mouse moving over the water leaves a wake. The water is a wave simulation on a
small grid, drawn in WebGL with the tiles bent by it; the buttons on top are the real navigation -->
<nav class="pool" aria-label="Main" data-pool>
<canvas class="pool__water" aria-hidden="true" data-water></canvas>
<span class="pool__lip" aria-hidden="true"></span>
<button class="tab" type="button" aria-current="page" data-tab>
<span class="tab__float" data-float>
<svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M3.5 18.5v-9M3.5 14.5h17v4M20.5 14.5v-2.3a2.7 2.7 0 0 0-2.7-2.7H11v5M7.4 12.2a1.7 1.7 0 1 0 0-.01"/></svg>
</span>
<span class="tab__name">Stay</span>
</button>
<button class="tab" type="button" data-tab data-autoclic>
<span class="tab__float" data-float>
<svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 9.5c1.5 0 1.5-1.2 3-1.2s1.5 1.2 3 1.2 1.5-1.2 3-1.2 1.5 1.2 3 1.2 1.5-1.2 3-1.2 1.5 1.2 3 1.2M3 14c1.5 0 1.5-1.2 3-1.2s1.5 1.2 3 1.2 1.5-1.2 3-1.2 1.5 1.2 3 1.2 1.5-1.2 3-1.2 1.5 1.2 3 1.2M3 18.5c1.5 0 1.5-1.2 3-1.2s1.5 1.2 3 1.2 1.5-1.2 3-1.2 1.5 1.2 3 1.2 1.5-1.2 3-1.2 1.5 1.2 3 1.2"/></svg>
</span>
<span class="tab__name">Pools</span>
</button>
<button class="tab" type="button" data-tab>
<span class="tab__float" data-float>
<svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 19.5c-2.6-1.6-3.9-4.2-3.9-7.2S9.6 6.6 12 4.5c2.4 2.1 3.9 4.8 3.9 7.8s-1.3 5.6-3.9 7.2zM12 19.5c-3.6.2-6.9-1.3-8.5-4.4 1.5-1 3.1-1.5 4.7-1.5M12 19.5c3.6.2 6.9-1.3 8.5-4.4-1.5-1-3.1-1.5-4.7-1.5"/></svg>
</span>
<span class="tab__name">Spa</span>
</button>
<button class="tab" type="button" data-tab data-autoclic>
<span class="tab__float" data-float>
<svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M7 3.5v5.2a2 2 0 0 0 4 0V3.5M9 3.5v17M16.5 20.5V3.5c-2 1-3.2 3.4-3.2 6.4 0 2 .9 3.3 3.2 3.5"/></svg>
</span>
<span class="tab__name">Dining</span>
</button>
<button class="tab" type="button" data-tab data-autoclic>
<span class="tab__float" data-float>
<svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 11.6a3.6 3.6 0 1 0 0-7.2 3.6 3.6 0 0 0 0 7.2zM4.8 20c.9-3.6 3.7-5.6 7.2-5.6s6.3 2 7.2 5.6"/></svg>
</span>
<span class="tab__name">You</span>
</button>
</nav>
</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.
20 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 pool tab bar” from Animorio and fit it into my siteUnder the bar lies a grid of 4 pixel cells (about 106 by 19 at 420 pixels). At every step a cell's new height is twice its height now, less its height before, plus WAVE_SPEED times the Laplacian (how much higher or lower its four neighbours are), all times DAMPING. The edges copy the cell next to them, so the waves bounce off the stone. There are 120 steps a second whatever the screen's rate, and the sum of the heights says when the water is calm.
A tap digs a bell-shaped hollow 9 pixels wide into the grid, right where the finger is (pointerdown, which comes before the click); the preview's click and the keyboard, which have no point, drop it in the middle of the tab. The mouse moving over the water leaves small hollows every 3 pixels of travel, deeper the faster it goes: the wake.
Every frame the heights go into a LUMINANCE texture of one byte a cell, read with linear filtering. The shader takes the slope from the neighbouring cells and moves the point where it reads the tiles and the light by as much: that is the refraction, which makes the joints sway under the rings. The heights' Laplacian says where the surface acts as a lens and gathers the light into bright lines, and the normal gives a small white glint on the crests.
The net of light on the bottom is drawn in the shader with two families of wavy lines, bright where their sum goes through zero, which move with a phase. The sun is a soft patch that multiplies the net and warms it; its position follows the middle of the chosen tab on a spring (SUN_SPRING). The phase moves on only while the water moves and slows down with it, so the net comes slowly to rest instead of freezing at once.
Every icon sits in a wrapper that every frame reads the water's height and slope under it: it rises and falls three pixels per unit of height and leans with the slope, up to 14 degrees. The icon inside answers the pointer through the CSS, so the two transforms do not step on each other.
| Name | File | Default | What it changes |
|---|---|---|---|
SPLASH | script.js | 1.4 | How hard a tap drops into the water. |
WAKE | script.js | 0.3 | How much a mouse moving over the water stirs it (0, not at all). |
DAMPING | script.js | 0.986 | How long the ripples last: closer to 1, longer. |
WAVE_SPEED | script.js | 0.24 | How fast the ripples run across the pool (up to 0.45). |
REFRACTION | script.js | 6 | How far the ripples bend the tiles, in pixels. |
SUN_SPRING | script.js | [90, 15] | The spring the sun glides to a tab on: how stiff, and how much it is held back. |
TILE | script.js | 13 | The size of a mosaic tile, in pixels. |
For the bottom bar of an app that deals with water and wellbeing: a thermal spa hotel, a spa, a swimming pool, a beach club, a seaside resort, a swimming school. The colours of the water, the tiles, the joints and the sun are in the variables: with white tiles and light water it becomes a city pool, with dark tiles a Turkish bath.
The bar is at most 420 pixels wide and on a phone it narrows to the screen with a 16 pixel margin each side plus the stone rim (358 on a 390 phone); it stays 70 tall and every tab is a target of about 69 by 70 pixels. The drop falls where the finger touches, and the water's grid is rebuilt at the new size.
The water stays still: no drops, no wake and icons that do not rock. Choosing another tab the sun is already on it and the pool is redrawn in a short fade; the net of light stays still. When the page opens the bar fades in.
GSAP 3.15.0, Jost (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 8.6 KB; the libraries: GSAP 28 KB.
Tested on 19 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.