
The inflatable button
Buttons, Physics, WebGL, 3D
NewFree
The Studio monitor setting of a recording app, and the switch is an old toggle from a studio console: a polished brass lever standing out of a threaded bushing, held by a hex nut to a plate of oxblood bakelite, with OFF and ON engraved and filled with paint. It is all drawn in 3D in a shader: clicked, a finger pushes the lever against its spring up to the dead centre, then the spring throws it over, it knocks its stop and bounces; the reflections run along the brass and its shadow sweeps the plate.
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 brass toggle switch</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the Studio monitor setting of a recording app, and the switch is an old brass toggle on a plate of bakelite,
the kind on a studio console: a bat handle lever in a threaded bushing with a hex nut, OFF and ON engraved
and filled with paint. The lever is drawn in 3D, ray marched in a shader: flicked or dragged, it is pushed
against its spring up to the dead centre, snaps over and knocks its stop with a little bounce, while the
reflections run along the brass and its shadow sweeps across the plate -->
<div class="tile" data-studio>
<span class="tile__text">
<label class="tile__name" for="monitor-switch" data-name>Studio monitor</label>
<span class="tile__state" id="monitor-state"><span class="tile__word" data-state>Speakers muted</span></span>
</span>
<button class="toggle" id="monitor-switch" type="button" role="switch" aria-checked="false" aria-describedby="monitor-state" data-switch data-autoclic>
<span class="toggle__drop" aria-hidden="true"></span>
<canvas class="toggle__art" aria-hidden="true" data-art></canvas>
</button>
<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.
26 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 brass toggle switch” from Animorio and fit it into my siteThe plate is a button with role switch and aria-checked, a label for with the name and aria-describedby on the line under it, which changes with the state. 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 says the new state. The focus ring only shows when the keyboard is in use.
The shader casts a ray for every pixel and steps along it until it touches something (ray marching): every part is a function that says how far away it is. The plate is an extruded rounded rectangle with a rounded edge, the nut a hexagonal prism cut by two cones, the bushing a tube with a helical thread (its radius changes with the sine of the height plus the angle), the lever a cone with round ends and a ball, turned about its pivot by the lever’s angle. Far from the lever the distance to its box is enough, so the pixels of the plate cost little.
Round the switch is a studio that only exists as a function of direction: a low softbox on the left (the key light), a strip on the right, a panel above, a pale band low all round and a warm bounce from below. The metals reflect that studio through a Fresnel term, more blurred the rougher they are, and under the horizon of the plate they reflect the bakelite; the bakelite has its own diffuse light and a glossy coat over it. The lever’s shadow is found by marching from the point towards the key light, softened by how close the ray passes to it, and rays that miss the lever’s box are lit straight away.
OFF, ON and the hairline round the edge are written white on black on a canvas sized in powers of two, a little blurred, and the shader reads them as how deep the groove is: inside is the paint, on the walls the slope bends the normal and darkens the occlusion. The canvas has mipmaps, so the hairline stays whole even with the plate seen at a slant.
The lever has an angle and a speed worked out four times a frame on gsap.ticker, with the time step held to 1/30 of a second. The spring always pushes it away from the centre, a little at the centre and more towards the stops (SPRING), so it can only rest against one of them; at the stop it bounces keeping BOUNCE of its speed and the plate gives a little jolt with an elastic settle. A click is a finger: a GSAP tween that speeds up to the dead centre (PUSH), then the spring alone. Dragged, the lever follows the finger with some resistance and past the centre it gets away.
The camera looks at the plate from below (VIEW) and the mouse swings it round the switch about its own up direction, so the plate never tilts sideways; under the mouse the lever leans the other way (PEEK). The shader redraws only when the lever’s angle or the camera changes. Where WebGL is missing the same switch is drawn on a flat canvas with the same camera: gradients, a shadow and a lever that turns.
| Name | File | Default | What it changes |
|---|---|---|---|
THROW | script.js | 34 | How many degrees the lever leans to each side. |
SPRING | script.js | 260 | How hard the spring throws the lever over the dead centre: higher, it snaps faster. |
BOUNCE | script.js | 0.42 | How much of its speed the lever keeps when it knocks its stop (0, it stops dead). |
PUSH | script.js | 0.15 | The seconds the finger takes to push the lever up to the dead centre. |
PEEK | script.js | 5 | How many degrees the lever leans the other way under the mouse. |
TILT | script.js | 7 | How many degrees the plate turns towards the pointer. |
VIEW | script.js | 50 | How far below the plate it is seen from, in degrees: higher, the lever is seen more in profile. |
POLISH | script.js | 0.16 | How rough the brass is: 0 a mirror, 0.5 brushed. |
LABELS | script.js | ['OFF', 'ON'] | The two words engraved in the plate, left and right. |
STATES | script.js | ['Speakers muted', 'Speakers live'] | The line under the name, off and on. |
For a switch that needs weight: the monitor or the microphone in a recording or music app, the night mode of a photography site, a setting in a home automation app, an important choice in a product configurator, the control panel of a site with a vintage or industrial feel. The snap and the metal say on even without reading. It is a piece to set on its own, where it is noticed, not in a long list of settings.
Same size as on a computer: the switch is 184 by 56 pixels and at 390 pixels the name and the plate sit on one line; under 360 pixels the plate shrinks a little, the drawing with it. A tap turns it on and off with the same snap; as soon as the finger rests on it the lever leans a little the other way and the plate turns towards the finger, as with the mouse. The finger can also drag the lever sideways, while up and down the page scrolls.
No movement: the lever does not turn, the plate does not swing or jolt. Turned on or off, the drawing fades for a moment and comes back with the lever on the other side, and the line under the name changes with a fade. Clicks, taps and keys work as always.
GSAP 3.15.0, Archivo (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 16 KB; the libraries: GSAP 28 KB.
Tested on 17 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.