Skip to content
animorio
IT
All components

The ribbon toast

Notifications, Transitions, PhysicsCanvas, GSAPAdvancedCompressed weight: Demo 12 KBGSAP 28 KBFreeDownload the zip

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.

The code

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>

Playground

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

Or ask Claude Code for it

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 site

What it does

On a white background a light grey button, Send message, and below it, at the bottom of the window, the place of the notification: 380 pixels wide and 70 tall. Pressing the button the notification arrives as a dark satin ribbon turning to face you from the left: the narrow tip of the twist runs ahead, and behind it the ribbon lies flat with the turning ring, Sending message… and To Marta Rossi. A moment later the message has gone and the ribbon twists again: a twist starts at the icon and runs to the other end, and behind it comes the other face, blue, with the tick drawing itself in its ring, Message sent, Delivered to Marta Rossi and Undo. Where the ribbon turns to the light a glint passes, where it turns down it darkens, and the free end swings a little past and back. Undo twists the ribbon once more, to the dark face with the arrow turning back: Sending undone, Back in your drafts. After five seconds (two and a half after Undo) the ribbon leaves turning its edge to you, again from left to right. With the pointer over it the ribbon bends a little right there, back above the middle line and forward below it, the light slides over it and the notification waits; it closes with its cross, with Esc, or dragged sideways or down. Every click sends a new message to another person, and if the ribbon is still turning another twist starts behind the first. When the page opens a first message goes by itself.

How it works

The ribbon is a chain

Along 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.

The twist is a solitary wave

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.

The faces are counted

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.

Painted in columns

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.

Light and shadow

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.

The real buttons

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.

Values to change

NameFileDefaultWhat it changes
SENDINGscript.js1.4The seconds the message takes to go.
LIFEscript.js5The seconds the notification stays once the message has gone (it waits under the pointer).
AFTER_UNDOscript.js2.6The seconds it stays after Undo.
TWISTscript.js0.55The seconds a twist takes to run along the ribbon.
LENGTHscript.js70How long the twisting stretch of ribbon is, in pixels.
WHIPscript.js0.6How far the free end swings past and back: 0 not at all, 1 a lot.
LEANscript.js9How far the ribbon bends under the pointer, in degrees.
SHEENscript.js0.6The glint of light on the turning ribbon: 0 none, 1 strong.
DEPTHscript.js480How far away the eye is, in pixels: less looks deeper.
PEOPLEscript.js['Marta Rossi', 'Jonas Weber', 'Priya Nair', 'Leo Brandt']Who the messages go to, in turn.
WORDSscript.jsSending message…, Message sent, Sending undone, Back in your draftsThe words of the states: sending, sent, undone and the line under it after Undo.

Try them in the Playground

When to use it

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.

On phones

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.

With reduced motion

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.

Pitfalls

  • Under the pointer the ribbon's top edge dropped by a whole pixel, in a step: drawImage at a fractional height does not soften the edges of the rectangle. Two empty rows above and below every face, stretched with it, make the edge soft.
  • With reduced motion the fade from one face to the next put two lines of words on each other halfway: now it goes through the bare ribbon, first the old words leave and then the new ones come.
  • The shadow with shadowBlur painted again every frame, and two fillRect of light for every column, cost more than the ribbon itself: in the cloud's Chrome without a graphics card a still frame cost 5 milliseconds and a twist 13 on the phone. Now the shadow lies on a canvas of its own at half size, painted again only when the outline changes, and the light is a single row of pixels: 0.2 milliseconds at rest and 6 during a twist.
  • With a short TWIST the chain's waves get faster than the 1/480 of a second step and the sums blow up: the step now gets shorter with the speed of the waves (at most half of one over √K), and the chain stays steady at every end of the knobs.

Libraries

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.

Testing

Tested on 5 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: To do

License

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.

All components