
The water button
Buttons, SVG, Physics, Hover
NewFree
Three switches from the settings of a design tool, and each one is a small spirit level in anodised aluminium that rocks on a pin. Turned on, the level tips with its right end up and the bubble, a real mass in a thick liquid, lags, runs uphill stretching into a lozenge and comes to rest round between the marks on the right, while the liquid warms and the name lights up. Under the mouse the level leans a little towards the pointer, and it can be grabbed and tipped by hand.
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 spirit level switch</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/geist@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- three switches from the settings of a design tool, each one a small torpedo level in anodised aluminium that
rocks on a pin, with a glass vial of yellow-green spirit and an air bubble in it. Switched on, the level tips
with its right end up and the bubble runs up to the marks at that end; off, it tips the other way. The
pointer's weight leans it a little, and it can be grabbed and tipped by hand -->
<div class="lv" data-levels>
<div class="lv__row lv__row--cobalt" data-row>
<label class="lv__name" for="lv-snap">Snap to grid</label>
<button class="lv__switch" id="lv-snap" type="button" role="switch" aria-checked="false" aria-describedby="lv-snap-hint" data-switch data-autoclic>
<svg class="lv__art" viewBox="6 4.5 228 57" aria-hidden="true" focusable="false" data-art></svg>
</button>
<span class="sr" id="lv-snap-hint">Objects lock to the 8 px grid</span>
</div>
<div class="lv__row lv__row--graphite" data-row>
<label class="lv__name" for="lv-rulers">Show rulers</label>
<button class="lv__switch" id="lv-rulers" type="button" role="switch" aria-checked="true" aria-describedby="lv-rulers-hint" data-switch>
<svg class="lv__art" viewBox="6 4.5 228 57" aria-hidden="true" focusable="false" data-art></svg>
</button>
<span class="sr" id="lv-rulers-hint">Rulers along the top and left</span>
</div>
<div class="lv__row lv__row--ember" data-row>
<label class="lv__name" for="lv-guides">Smart guides</label>
<button class="lv__switch" id="lv-guides" type="button" role="switch" aria-checked="false" aria-describedby="lv-guides-hint" data-switch>
<svg class="lv__art" viewBox="6 4.5 228 57" aria-hidden="true" focusable="false" data-art></svg>
</button>
<span class="sr" id="lv-guides-hint">Lines appear as edges align</span>
</div>
<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.
49 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 spirit level switch” from Animorio and fit it into my siteEach level is a button with role switch and aria-checked, with its own label for (the name) and aria-describedby on a hidden sentence that says what it does: Enter and Space turn it on and off, the right arrow turns it on and the left one off, and so does a click on the name. An aria-live line announces the new state. The three rows sit in a grid of two shared columns (subgrid), so the names and the levels stay lined up.
The script draws the SVG of each level once: the bar with a gradient in the colours of its anodising, a brushed grain of random hairlines (always the same for a level), the sheen, the bright edges, the rubber caps with their ridges, the engraved O and I, the window with its edge catching the light at the bottom and the shadow of the lip at the top. The vial is a capsule bent along an arch: two quadratic Béziers above and below, two half circles at the ends. The viewBox is cut to the level tipped all the way, so the switch is 56 pixels tall and the level, on or off, fills them without going out; the rotation is a CSS transform about the pin (transform-origin at 77.19 per cent).
The vial is arched, so its highest point moves along the vial as the level tips: at the full tilt TILT it sits between the marks. The bubble is pulled towards that point with a force that grows with the distance, and held back by the liquid: an acceleration of ω²(top − s) − 2ζω·v, with ω from SPEED and ζ from DRAG, different for each bubble. It is worked out in four steps a frame on gsap.ticker, with the time step held to 1/30 of a second; the ends of the vial stop it with a small knock. The tilt itself is a GSAP tween, so the bubble lags and then speeds up.
The bubble’s length follows its speed through a small stiff spring, so it wobbles once as it stops; its height is the area over the length, never more than the glass allows. The outline is a superellipse of 48 points, flat on top where the bubble presses on the glass, with a squarer head and a tail that thins from below as much as it is fast. The meniscus is three evenodd bands between outlines further and further in, dark outside and lighter inside, thicker at the ends; over them, a gleam along the top edge, a bright rim at the bottom and a speck of light.
The four marks are drawn again every frame as lines of 19 points. For each point the distance from the bubble is measured in the bubble’s own units: inside the bubble the mark is seen smaller (0.76 times, towards its middle), outside it is pushed outwards by LENS·e^(−2.4·(d−1)), so it bends round the bubble; where it crosses the rim the line breaks, instead of cutting through the meniscus with a slanted stroke.
Under the mouse, a soft spring leans the level by up to LEAN degrees towards the pointer’s side. Dragged past 4 pixels it captures the pointer: lifting the point held turns it about the pin and pulling it sideways tips it that way, up to GRAB times TILT; let go, it goes to the nearer state, and the click that follows the drag does not count. The colour of the liquid follows the tilt (the gradient’s stops are mixed again only when it changes), the light of the name follows the bubble (--lit on the row). Every frame a key of rounded values is compared: when nothing changes, nothing is drawn.
| Name | File | Default | What it changes |
|---|---|---|---|
TILT | script.js | 4 | How far a level tips for on (right end up) and off (left end up), in degrees. |
TIP | script.js | 0.55 | The seconds a switched level takes to tip over. |
LEAN | script.js | 0.75 | How far the pointer’s weight leans a level under the mouse, in degrees (0, not at all). |
SIZE | script.js | [1, 0.86, 1.12] | How big each level’s bubble is, from top to bottom. |
SPEED | script.js | [8.2, 6, 10.5] | How quickly each bubble answers the slope. |
DRAG | script.js | [0.74, 0.9, 0.68] | How much the liquid holds each bubble back: below 1 it goes a little past the mark and comes back. |
STRETCH | script.js | 0.6 | How much longer the bubble gets as it runs (its area stays the same, so it gets thinner). |
LENS | script.js | 2.6 | How far the liquid round the bubble bends the marks behind it (0, no lens). |
GRAB | script.js | 1.8 | How far a level can be tipped by hand, as a multiple of TILT. |
For the switches of a tool that is about lining up and measuring: the settings of a design or CAD editor (grid, rulers, guides), an interior design or building site app, a tool for photographers or for hanging pictures. The bubble running between the marks says on and off by itself, even without colour. It works well in a small group as here; in a long list of settings it is best used for a few items, next to plain switches.
Same size as on a computer: each level is 224 by 56 pixels and at 390 pixels the name and the level still sit on one line (the group is 338 wide); on narrower screens the level shrinks with the screen. A tap turns it on and off; a finger dragged sideways tips the level and the bubble runs, while up and down the page still scrolls (touch-action: pan-y). No leaning under the mouse, which a finger does not have.
No movement: the level is at its new tilt at once and the bubble does not run, it fades out at one end and back in between the marks at the other in under half a second; the colour of the liquid and the light of the name change in a fade. No leaning under the mouse and no dragging; clicks, taps and keys work as always.
GSAP 3.15.0, Geist (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 13 KB; the libraries: GSAP 28 KB.
Tested on 7 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.