
The slide-to-confirm button
Buttons, Physics, Transitions
NewFree
A coffee bar's order button, with a cup of espresso seen from above in the round end of the pill. At a click the milk pours and the heart is not drawn: it is carried. The coffee's surface is a grid of 72 × 72 cells holding the milk and the crema's tone, and every frame a flow moves them (semi-Lagrangian advection): first the milk dives under the crema, then rises in a white disc, and a last thin pour drawn through the disc drags the crema in and the milk out, making the notch and the point of the heart. A moment later a spoon stirs the heart into a spiral and the cup is espresso again.
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 latte art button</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the latte art button: the order button of a coffee bar, with a cup of espresso seen from above in the round left
end of the pill. A click pours the milk: it dives under the crema, then rises as a white disc, and a last thin
pour drawn through it drags the crema in and the milk out into a heart. A moment later a spoon's stir turns the
heart into a spiral and the cup is plain espresso again -->
<div class="latte" data-latte>
<div class="latte__unit">
<button class="latte__btn" type="button" data-btn data-autoclic>
<span class="latte__fill" aria-hidden="true"></span>
<span class="latte__faces">
<span class="latte__face" data-face="rest">
<span class="latte__word" data-wide="Order a flat white" data-rest-text>Order a flat white</span>
<span class="latte__price" data-rest-price>€3.80</span>
</span>
<span class="latte__face latte__face--two" data-face="busy" aria-hidden="true">
<span class="latte__small" data-busy-small>Oat milk · to drink here</span>
<span class="latte__big" data-busy-text>Pouring your flat white</span>
</span>
<span class="latte__face latte__face--two" data-face="done" aria-hidden="true">
<span class="latte__big" data-done-text>Ready at the bar</span>
<span class="latte__small" data-done-small>Order 42 · €3.80</span>
</span>
</span>
</button>
<!-- the cup seen from above: saucer and cup, the coffee on a canvas, and over it the rim's shine and the milk's
stream; the pointer goes through them to the button -->
<div class="latte__cup" aria-hidden="true" data-cup>
<svg class="latte__china" viewBox="-40 -40 80 80" data-china></svg>
<canvas class="latte__coffee" data-coffee></canvas>
<svg class="latte__over" viewBox="-40 -40 80 80" data-over></svg>
</div>
</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.
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 latte art button” from Animorio and fit it into my siteThe coffee is a 46-pixel canvas with two fields on a grid of 72 × 72 cells over it: how much milk there is (0 crema, 1 white foam) and the crema's tone (0 its dark flecks, 1 its golden light). The crema of a fresh espresso comes from two Perlin noises, a broad one for the mottling and a fine one for the flecks, plus a few rings and a lighter middle. Every frame the grid is coloured into a 72 × 72 ImageData and drawn enlarged and clipped to the cup's circle.
The cells move along a flow written as the sum of three things: a whole turn of the coffee (speed −y·ω, x·ω), a source where the milk lands (a radial speed q / 2π · d / |d|², which spreads the milk by q square pixels a second) and a thin jet where the pour is dragged (a narrow Gaussian in the direction it moves). Against the wall the flow dies. Each cell looks back along the flow, in two half steps so curved paths stay curved, and takes the milk and crema that were there (semi-Lagrangian advection, read bilinearly).
A GSAP timeline moves the flow's numbers as a hand would: for half a second the milk falls from high up and dives (the crema lightens around it and the milk stays under 32%); then the source rises to 580 square pixels a second and lays a white disc; last, the source stops and the jet runs from top to bottom through the disc in 0.42 seconds. At the start of its run it drags crema into the disc and makes the notch, at the end it drags milk out and makes the point: the latte heart comes from the barista's own gesture.
Each cell's colour mixes the crema (from its tone, darker towards the wall, where it is thinnest) and the milk, with a crisp edge between 0.3 and 0.6 of milk and a brown halo where the milk thins over the crema. Over the canvas an SVG lays the wall's shadow, the shine on the liquid and the cup's rim; the stream of milk is a small white disc with its shadow.
The spoon is a turn of the flow up to 7 radians a second while the milk fades: the heart winds into a spiral and sinks, then the crema is the one it started with. Under the pointer the cup turns on a spring and the coffee follows it late, dragged by the wall: so as not to blur the crema at every pass, the whole coffee is drawn turned instead of being carried cell by cell. The button is a real button with aria-disabled while it pours, an aria-live region that says Pouring your flat white… and then Ready at the bar.
| Name | File | Default | What it changes |
|---|---|---|---|
LABEL | script.js | Order a flat white | The words at rest. |
PRICE | script.js | €3.80 | The price beside the words, and again underneath once it is ready. |
DETAILS | script.js | Oat milk · to drink here | The small line while the milk pours: the milk chosen and where it is drunk. |
POURING | script.js | Pouring your flat white | The words while the milk pours; the aria-live region says them too. |
READY | script.js | Ready at the bar | The words once the heart is made. |
TICKET | script.js | Order 42 | The small line under them, before the price: the order's number. |
POUR | script.js | 1 | How slow the pour is: 0.6 brisk, 1.6 slow. The heart comes out the same. |
HOLD | script.js | 2.6 | The seconds the heart stays before the spoon stirs it away. |
HEART | script.js | 1 | How much milk goes into the heart: 0.6 a small one, 1.3 a big one that nearly reaches the rim. |
TURN | script.js | 14 | How far the cup turns under the pointer, in degrees. |
For the order button of a coffee bar, a roastery, a café that takes orders by phone or a hotel's breakfast, and for the Add to cart of anyone selling coffee, plant milk or cups. The button fakes the order: in a real site the click sends the order and the milk pours while the server answers.
The pill is about 300 pixels wide and the saucer stands 5 pixels past it: at 390 pixels it fits. A tap nudges the cup on its saucer, squashes the fill and orders. The coffee's grid is the same, and the drawing stays sharp on dense screens.
No pour and no turning: the heart is worked out at once, out of sight, and fades in on the cup; the spoon is a fade that brings the crema back. The cup does not turn under the pointer, the pill does not swell and the words do not widen (under the mouse the fill only lightens a little).
GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 12 KB; the libraries: GSAP 28 KB.
Tested on 19 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.