
The slide-to-confirm button
Buttons, Physics, Transitions
NewFree
The notification of a message being sent, made like a satin ribbon: every change of state is a twist that starts at the icon and runs along the ribbon to its free end, which swings a little past and back, and the face that turns up carries the new state. The ribbon arrives turning to face you from the left and leaves turning its edge to you; under the pointer it bends a little and the light slides over it.
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, viewport-fit=cover">
<title>The ribbon toast</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/onest@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the button only sends a message: the notification at the bottom of the window is the component. It is a
satin ribbon painted on a canvas: every change of state (sending, sent, undone, gone) is a twist that runs
along it from the icon and shows its other face. The real buttons sit on top of the painting, transparent,
and the words are read out by the status line -->
<button class="send" type="button" data-send data-autoclic>Send message</button>
<span class="floor" aria-hidden="true"></span>
<section class="toaster" aria-label="Notifications" data-toaster>
<div class="toast" data-toast inert>
<canvas class="toast__paint" aria-hidden="true" data-shade></canvas>
<canvas class="toast__paint" aria-hidden="true" data-paint></canvas>
<button class="toast__undo" type="button" data-undo hidden>Undo</button>
<button class="toast__close" type="button" aria-label="Dismiss notification" data-close></button>
</div>
<p class="sr" role="status" aria-live="polite" aria-atomic="true" data-live></p>
</section>
</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 ribbon toast” from Animorio and fit it into my siteAlong the ribbon there are 48 points, each with its angle θ. A torsion spring ties every point to its two neighbours, and a potential G·sin²θ, with its hollows at face up and face down, makes the ribbon want to lie flat: θ'' = K(θ of the neighbours − 2θ) − G·sin 2θ − C·θ' + F·sin²θ. GSAP turns the first point half a turn in 0.22 seconds (power2.inOut), the last one is free. The chain moves in steps of 1/480 of a second, or shorter when the wave is faster, with the frame's step limited to 1/30.
In such a chain (the sine-Gordon equation) a half turn travels as a kink: a wave that keeps its shape, √(K / 2G) points wide. The push F·sin²θ is zero on both faces, so it does not tilt the ribbon at rest, but it gives energy to every point standing on edge and carries the kink at a steady speed to the end. The constants come from the values: K from TWIST, G from LENGTH, C from WHIP, and F from the speed of a damped kink (McLaughlin and Scott), here at six tenths of the speed of the waves. At the free end the kink makes it swing past, and the potential's hollows bring it back.
Every half turn shows the next face, so the faces are counted: a point turned k half turns shows face k. Every change of state (sending, sent, undone, gone) takes the next number and turns the first point to it. Before it arrives and after it leaves the ribbon shows an empty face: so the entrance and the exit are twists too. With quick clicks several kinks run in a row: two kinks turning the same way push each other away, so they never merge.
Every face is a hidden canvas, upright, with two empty rows above and below. The ribbon is painted one column of pixels at a time: the angle comes from the two nearest points, and columns with the same angle go in a single drawImage (at rest one for the whole ribbon, and the words stay sharp). Each column is its face squeezed by the cosine of its angle, in perspective: the edges are at ±h·cos φ · D / (D ± h·sin φ), with D = DEPTH. The back is painted upright, so after half a turn it reads.
The lamp is above and in front: a face turned down darkens, one turned up takes a little light, and passing through the angle that sends the lamp into the eye it glints (a cosine to the sixtieth). A black veil of a and a white one of b are one veil of alpha 1 − (1 − a)(1 − b) and grey 255·b / alpha: a row of pixels with one veil per column, stretched over the ribbon in source-atop. The shadow lies on a canvas of its own under the ribbon, at half size, painted again only when the outline changes: the shape is drawn far away and only its shadow, moved back, lands on the canvas.
Over the painting lie the real buttons, transparent, placed by the same numbers that paint the painted ones; the words are read by a role="status" line. The time to leave is a GSAP delayedCall that stops with the pointer over it, the focus inside, a finger holding it and the page hidden. Esc sends it away; dragged, it picks its direction after six pixels and past 100 sideways or 36 down, or thrown, it flies off. The focus ring shows only while the ribbon lies flat.
| Name | File | Default | What it changes |
|---|---|---|---|
SENDING | script.js | 1.4 | The seconds the message takes to go. |
LIFE | script.js | 5 | The seconds the notification stays once the message has gone (it waits under the pointer). |
AFTER_UNDO | script.js | 2.6 | The seconds it stays after Undo. |
TWIST | script.js | 0.55 | The seconds a twist takes to run along the ribbon. |
LENGTH | script.js | 70 | How long the twisting stretch of ribbon is, in pixels. |
WHIP | script.js | 0.6 | How far the free end swings past and back: 0 not at all, 1 a lot. |
LEAN | script.js | 9 | How far the ribbon bends under the pointer, in degrees. |
SHEEN | script.js | 0.6 | The glint of light on the turning ribbon: 0 none, 1 strong. |
DEPTH | script.js | 480 | How far away the eye is, in pixels: less looks deeper. |
PEOPLE | script.js | ['Marta Rossi', 'Jonas Weber', 'Priya Nair', 'Leo Brandt'] | Who the messages go to, in turn. |
WORDS | script.js | Sending message…, Message sent, Sending undone, Back in your drafts | The words of the states: sending, sent, undone and the line under it after Undo. |
For an action that has an outcome and a way back: a message going out, a payment, a file in the bin, a post going live. One notification at a time, with a line of title and one of detail; for many notifications together there is the pile. It suits places where the confirmation should be noticed without stopping the person at work.
The notification takes the width of the screen minus 32 pixels (358 at 390) and sits at the bottom, above the safe area; the ribbon is painted at the screen's real density, up to three times. A finger sends it away dragging it sideways or down, and held it bends where the finger holds it; Undo and the cross have a touch area bigger than the painted one.
No twists and no bending: the ribbon stays flat and changes state in a fade, first the old words vanish under the new colour and then the new ones come, so they never lie on each other. It arrives and leaves in a fade, the ring turns slowly without stretching, the tick and the arrow are already drawn. Dragging it away still works, and it fades instead of flying.
GSAP 3.15.0, Onest (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 12 KB; the libraries: GSAP 28 KB.
Tested on 5 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.