
The slide-to-confirm button
Buttons, Physics, Transitions
NewFree
The Haptic feedback switch of a phone’s settings, and the track is a Chladni plate: black steel with a film of sand on it. Turned on, the knob buzzes like the phone and the plate rings: in a second the sand runs from where the plate moves, gathers where it stays still and draws one of its figures. Turned off, the knob knocks the end and the sand jumps and lands scattered.
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 haptics switch</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/schibsted-grotesk@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the Haptic feedback switch of a phone's settings, and the track is a Chladni plate: a slab of black steel with
a little sand scattered on it. Turned on, the plate buzzes the way the phone would, and the sand runs off
where the plate moves and gathers where it stays still, drawing one of the plate's figures; turned off, the
knob knocks the end of the track and the sand jumps and lands scattered -->
<div class="hap" data-hap>
<label class="hap__label" for="hap-switch" data-label>Haptic feedback</label>
<button class="hap__switch" id="hap-switch" type="button" role="switch" aria-checked="false" data-switch data-autoclic>
<span class="hap__plate" aria-hidden="true"></span>
<span class="hap__plate hap__fill" aria-hidden="true" data-fill></span>
<canvas class="hap__sand" aria-hidden="true" data-sand></canvas>
<canvas class="hap__sand hap__was" aria-hidden="true" data-was></canvas>
<span class="hap__shade" aria-hidden="true"></span>
<span class="hap__knob" aria-hidden="true" data-knob>
<span class="hap__puck" data-puck></span>
</span>
</button>
</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.
21 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 haptics switch” from Animorio and fit it into my siteIt is a button with role switch and aria-checked, with its own label for: Enter and Space turn it on and off, so does a click on the words, and its name for screen readers is the words. Inside are the plate twice, off and on (the second one’s opacity follows the knob along the track), two canvases for the sand (the second one is for the reduced motion fade), a layer with the inner shadow, and the knob: an element that runs and buzzes, and inside it a disc that squashes against the end and sinks under a finger.
The plate rings as two of its modes added together, cos(n π x) cos(m π y) plus MIX times the second one, on the free part of the plate, the part left of the knob when it is on: it is a Chladni figure. Every figure is worked out once only on a one pixel grid, with its exact slopes, normalised to 1; then it is read between the pixels with bilinear interpolation. Where it is zero lies a nodal line: there the plate does not move.
3200 grains (GRAINS). On every frame each one takes a random jump KICK times as big as the plate moves under it (never less than LINE, which gives the lines their thickness) and slides down the slope of u², towards the nearest line: it is a random walk whose step vanishes on the lines, and the sand comes to rest there by itself. The jump grows with the square root of the time step and the slide with the step, so the speed does not depend on the screen’s hertz. Along the rim of the knob the plate shakes the most and no line stays there; with the knob still at an end the sand is thrown off the rim, and the sand in the strip behind it slips under it. Three batches of little squares are drawn in three tones, over their shadows, and they tremble in the drawing as much as the plate under them.
At the off end the knob’s speed as it arrives sets the strength of the knock, which runs through the plate at 650 pixels a second: every grain it reaches jumps along a parabola (in the air it is drawn higher and bigger, and its shadow stays on the plate) and lands the farther the nearer it is. A hard knock also evens the sand out along the track again: the grains, in their order along x, take one by one the places, in order, of freshly scattered sand, the optimal transport in one dimension. Whatever goes past the edge bounces back in.
The knob is a mass on a spring (SPRING, damped below critical) with two walls: it bounces (BOUNCE) and squashes on the wall’s side. Dragged past 5 pixels it captures the pointer and follows the hand on a stiffer spring, and the plate buzzes at a frequency that depends on where it is: the three figures each have a resonance along the track, at 0.36, at 0.68 and at the end, and their weight is a Lorentz curve. Let go, a fast flick decides by its direction, otherwise the half it is in, and the click that follows is ignored. The focus ring shows for the keyboard, not after a click.
| Name | File | Default | What it changes |
|---|---|---|---|
GRAINS | script.js | 3200 | The grains of sand on the plate. |
MODES | script.js | [4, 2, 6, 0] | The figure the sand draws: two ways the plate rings, each as waves across and up. |
MIX | script.js | 0.65 | How much of the second way rings with the first: below zero the figure turns inside out. |
BUZZ | script.js | 1.2 | The seconds the plate buzzes once it is turned on. |
KICK | script.js | 2.6 | How far the buzz throws a grain where the plate moves most, in pixels a frame. |
LINE | script.js | 0.07 | How thick the lines of sand are: the little shake left where the plate stays still. |
KNOCK | script.js | 14 | How far the knock at the off end throws the sand, in pixels. |
SPRING | script.js | 170 | How hard the spring pulls the knob to its end of the track. |
BOUNCE | script.js | 0.3 | How much of its speed the knob keeps bouncing off the end (0, no bounce). |
PEEK | script.js | 4 | How far the knob leans towards the other side under the mouse, in pixels. |
For a setting you feel under your fingers: the vibration and haptic feedback of a phone, a watch or a controller, keyboard clicks, a metronome. The sand falling into order says by itself what the switch does. In a list of settings it is best used for one entry only, next to plain switches.
Same size as on a computer, 168 by 56 pixels: at 390 pixels the words and the switch still sit on one line, and on a narrower screen the words go above the switch. A tap turns it on and off; a finger dragged sideways takes the knob along and makes the plate ring, while up and down the page still scrolls (touch-action: pan-y). No leaning under the mouse, which a finger does not have. The sand is drawn at the screen’s density (up to 2) and worked out only while something moves.
The sand is a still picture: scattered when off, the figure already drawn when on (worked out ahead without drawing it). At a click the old picture fades away over the new one in three tenths of a second, the colour of the plate changes in a fade and the knob fades out at one end and back in at the other. No sand poured as the page opens, no buzz and no knock; pressed, it does not sink and cannot be dragged; clicks and keys work as always.
GSAP 3.15.0, Schibsted Grotesk (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 12 KB; the libraries: GSAP 28 KB.
Tested on 6 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.