
The layered button
Buttons, Hover, Physics
Free
The Use my location button of a delivery app, with a real liquid compass in the round end of the pill, where the icon would be: a knurled brass bezel, a printed card with its ticks and the red N, a two-coloured needle floating in the liquid, the glass dome and a small air bubble. The needle is a true torsion pendulum: the pointer near the button is a magnet, the needle turns to meet it, goes a little past and settles, and when the pointer leaves it swings back to north. At a click the needle hunts as if disturbed and the card turns a little; then it locks on north with a last bounce, a glint runs over the brass and the pill widens on Delivering to Brera, Milan.
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 compass button</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the compass button: the Use my location button of a delivery app, with a real liquid compass in the round left
end of the pill, where an icon would be. The needle is a torsion pendulum floating in the liquid: the pointer
near the button is a magnet, so the needle turns to it, goes a little past and settles, and swings back to
north when it leaves. A click looks for you (simulated, no real geolocation): the needle hunts, the card
turns, then it locks on north and the pill opens on the place found -->
<div class="locate" data-locate>
<button class="locate__btn" type="button" data-btn data-autoclic>
<span class="locate__fill" aria-hidden="true"></span>
<canvas class="locate__compass" aria-hidden="true" data-compass></canvas>
<span class="locate__faces">
<span class="locate__face" data-face="rest">
<span class="locate__word" data-wide="Use my location" data-rest-text>Use my location</span>
</span>
<span class="locate__face locate__face--busy" data-face="busy" aria-hidden="true">
<span class="locate__shine" data-busy-text>Locating…</span>
</span>
<span class="locate__face locate__face--found" data-face="found" aria-hidden="true">
<span class="locate__place">
<span class="locate__lead" data-lead-text>Delivering to</span>
<span class="locate__spot">
<svg class="locate__pin" viewBox="0 0 16 16" aria-hidden="true" data-pin>
<path d="M8 14.6c-.3 0-.6-.2-.8-.4C5.3 12 3.3 9.4 3.3 6.8a4.7 4.7 0 0 1 9.4 0c0 2.6-2 5.2-3.9 7.4-.2.2-.5.4-.8.4z"/>
<circle cx="8" cy="6.8" r="1.7"/>
</svg>
<span class="locate__name" data-place-text>Brera, Milan</span>
</span>
</span>
<span class="locate__change" data-change-text>Change</span>
</span>
</span>
</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.
28 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 compass button” from Animorio and fit it into my siteThe canvas covers the round end of the pill (60 pixels, at the screen's density up to 3). What does not move is painted once on two canvases off screen: below, the housing's shadow on the pill, the brass bezel (a conic gradient with two light lobes facing each other, the one towards the lamp the brighter), the knurling, the groove, the bevel with the gradient turned half round because it faces inwards, and the cream ground; above, the inner shadow of the rim (a ring outside the clip that throws its shadow in), the liquid deeper towards the rim and the reflections of the glass. Every frame only the turned card, the needle, the bubble and the glint of the lock are drawn, between the two still layers.
The needle is a lozenge with slightly concave sides, split by its ridge into four facets. A facet's light is the dot product of its normal on the screen (the needle's right, cos θ and sin θ, or its left) with the direction of the lamp at the upper left: as it turns, the light facet changes sides, and the needle reads as metal. The shadow on the card uses shadowOffset, which the canvas measures in screen pixels and does not turn with the transform: it always falls down and to the right, at any angle. The cap on the pivot is drawn afterwards, unturned, so its light stays put.
The needle's angle θ follows θ'' = ω0² · |B| · sin(β − θ) − c · θ', with ω0 = 2π · SWING. The field B is the sum of the earth's, up the page, and the pointer's: towards it, as strong as MAGNET · 1 / (1 + (d / REACH)³), falling with the cube of the distance like a magnet's and fading out within 16 pixels of the pivot, where it would pull straight down through the needle. The viscous damping is c = 2 · DAMPING · ω0 · √|B|: the stronger the field the faster the needle, and the liquid holds it back in proportion, so it always overshoots by about a third and then a little. Steps of 1/240 of a second, the frame capped at 1/30; when the needle has no speed or torque left, the bubble is still and nothing moves, the loop stops working and drawing until the pointer or an animation wakes it.
The bubble follows its place at the lower right on a soft spring (1.3 swings a second, damping 0.55). The liquid carries it: the liquid's speed where it is is 0.24 · θ' turned a quarter round, so it goes a little way round the pivot when the needle swings. The housing pushes it: the compass's position on the screen is read every frame, and its acceleration (finite differences, filtered and capped) moves it like a bubble in a glass being moved, for instance when the pill widens and the compass slides to the left. A press tips the compass away from the finger and the bubble's place moves to the high side, by PRESS · 7 pixels. The rim holds it: it brings it back inside and takes away its outward speed, with a small bounce.
While searching, the earth grows weaker and a wandering field comes in: its angle is gradient noise over two octaves (2.7 · n(0.8 t) + 1.3 · n(2.2 t)), with a strength of 1.6, and a tremor of fast noise on the torque. The card, on a spring of its own, follows a hand turning it by up to 0.42 radians with noise. It is all simulated with GSAP's timing: the browser never asks for the location. After LOCATE seconds the disturbance stops, the earth grows 2.6 times stronger and the damping one and a half times: the needle snaps to north with a bounce of about ten degrees and a small one. A banded glint runs over the whole housing in screen mode, and the pill widens to the measured width of the place found with back.out.
Three faces on top of each other, as wide as their words (width: max-content), measured to the fraction of a pixel and rounded up: the pill at rest already holds the room of the wide words (an invisible copy at font-stretch 116%), the found one is as wide as Delivering to, the place and Change, and on a narrow screen the name ends with an ellipsis. The button is a real button: aria-disabled while it searches and opens, an aria-live region says Locating… and Delivering to Brera, Milan, and once found the aria-label also says Change location. The search and the opening take about 3.3 seconds, more than the 2.8 between two clicks of the preview: the second click falls in the wait and the open pill stays in view for a whole round.
| Name | File | Default | What it changes |
|---|---|---|---|
LABEL | script.js | Use my location | The words at rest. If they are too long for the room, the type gets smaller. |
BUSY | script.js | Locating… | The words while it searches; it is also what the aria-live region says. |
LEAD | script.js | Delivering to | The small line over the place found. |
PLACE | script.js | Brera, Milan | The place found. The search is simulated: in a real site this is the address or the store your server finds. |
CHANGE | script.js | Change | The small pill right of the place found: a click takes the button back to rest. |
LOCATE | script.js | 2 | The seconds of the search, while the needle hunts and the card turns. |
SWING | script.js | 1 | The needle's swings a second in the earth's field alone: higher, a light quick needle; lower, a heavy slow one. |
DAMPING | script.js | 0.3 | How hard the liquid holds the needle back: 0.05 it swings a long time, 0.3 it goes a third past and settles, 1 it creeps to its place without passing it. |
MAGNET | script.js | 10 | How strong the pointer is as a magnet close up, in earth fields: 0 the needle does not feel it; the higher, the more squarely the needle points at it. |
REACH | script.js | 200 | How far the magnet reaches, in pixels: at this distance it pulls with half its strength, at twice the distance with a ninth. |
PRESS | script.js | 1 | How far a press tips the compass: the bubble slides away from the finger. 0 it stays put. |
For the button that finds where you are: the delivery address of a food or grocery app, the nearest store of a chain, the local weather, an address form, the search for a restaurant or a rental. The compass suits travel, hiking, sailing and well-made-things sites. It has to be wired to a real search (the Geolocation API or the address the user saved): here it is all simulated and the browser asks for nothing.
The pill at rest is about 225 pixels wide and the open one about 275: at 390 pixels they fit with room to spare, and if the place is longer than the screen the name ends with an ellipsis. A finger resting on the page near the button is the magnet and the needle turns to meet it; a tap on the button squashes the fill, slides the bubble and searches. The compass keeps its 46 pixels.
The needle stays still on north and does not follow the pointer; no swing on arrival, the pill does not swell and the words do not widen (under the mouse the fill only lightens a little). Searching, the needle fades to a third and Locating… fades in; found, the needle comes back full, the pill takes its new width while the words are gone and Delivering to Brera, Milan fades in, with no dropping pin and no glint. Another click goes back to rest the same way.
GSAP 3.15.0, Mona Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 16 KB; the libraries: GSAP 28 KB.
Tested on 12 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.