
The layered button
Buttons, Hover, Physics
Free
A pill of soap with colours swimming in the film: it breathes, leans towards the pointer, and pops when you press it. A hole opens from the point you touched and runs across the film, droplets fly off the rim, the words become “Invite sent” with a tick and, a moment later, the bubble blows back up with a bounce.
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 soap bubble button</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">
<!-- the soap bubble button: a pill of soap film with slow colours swimming in it. It breathes, leans towards the
pointer, and when it is pressed it pops: a hole opens from the point you touched and runs across the film,
droplets fly off the rim, the words become "Invite sent" and, a moment later, the bubble blows back up -->
<div class="soap" data-soap>
<button class="soap__btn" type="button" aria-label="Send invite" data-btn data-autoclic>
<span class="soap__film" aria-hidden="true" data-film>
<span class="soap__swirl" data-swirl></span>
<span class="soap__shine"></span>
</span>
<span class="soap__outline" aria-hidden="true" data-outline></span>
<span class="soap__label">
<span class="soap__word" aria-hidden="true" data-word>Send invite</span>
<span class="soap__word soap__word--done" aria-hidden="true" data-done>
<svg class="soap__tick" viewBox="0 0 20 20"><path data-tick d="M4.5 10.5l3.6 3.6 7.4-8.2"/></svg>
<span data-done-text>Invite sent</span>
</span>
</span>
</button>
<span class="soap__drops" aria-hidden="true" data-drops></span>
<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.
19 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 soap bubble button” from Animorio and fit it into my siteA pill with hidden overflow that holds a big conic gradient (the script writes it from TINTS), blurred by 12 pixels and turned slowly by GSAP. On top sit an oval white veil that clears the middle so the words read well, and two shines; the pink-under, blue-over rim is an inset box-shadow on a separate layer above the colours.
Two endless tweens of different lengths (scale one way, half a degree of rotation the other) so the shape never repeats. The whole button follows the pointer with three quickTo (x, y, rotation) on elastic.out; pressing scales it 1.035 across and 0.9 down, and on release it comes back with an elastic.
The film’s mask is a radial-gradient, transparent inside and black outside, centred on the click. A tween on a plain object grows its radius from 0 to the distance of the farthest corner, with power2.in, and on every frame the script rewrites the mask. When the hole is done the film goes to opacity 0 and the mask is removed.
Each starts on a point of the pill’s elliptical edge and leaves along the normal, which on a wide pill points mostly up and down. One tween throws it and a second makes it fall and fade. The delay is the distance from the click divided by the width, so the first to leave are the ones the hole reaches first. Each has a radial gradient with a white spot and a ring of the same colour.
At rest, popped, blowing up. The two lines of text sit in the same cell of a grid, so the button always has the same width; one rolls up and out, the other comes in from below with back.out. The tick is an SVG path drawn with strokeDashoffset. While popped the button has aria-disabled and an aria-live region says “Invite sent”.
| Name | File | Default | What it changes |
|---|---|---|---|
LABEL | script.js | 'Send invite' | The words at rest. |
DONE | script.js | 'Invite sent' | The words after the pop. |
DROPS | script.js | 26 | How many droplets fly off. |
REACH | script.js | 70 | How far the droplets fly, in pixels. |
HOLD | script.js | 2.4 | The seconds it stays popped before blowing back up. |
BREATH | script.js | [0.02, 4.5] | How much it swells and shrinks, and the seconds of one breath. |
POP | script.js | 0.22 | The seconds the hole takes to run across the film. |
TINTS | script.js | ['#ff7ac8', '#7ad0ff', '#b8ff8f', '#ffd978'] | The colours of the film, in turn. |
For an action you do once and watch happen: invite, send, unlock, redeem. Where the tone is light and the confirmation wants a small gesture of play.
Taps work like clicks and the hole starts from the point touched. With a finger there is no leaning towards the pointer and no faster colours; everything else is there.
No breathing, the colours still, no leaning or squashing. The film fades with a short dissolve, with no hole and no droplets; the words swap with a fade and the bubble comes back with a fade.
GSAP 3.15.0, Geist (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 5.5 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.