
The copy button
Buttons, Text, SVG, Physics
NewFree
In the minimal style: the Save for later of a reading app, a thin ink pill on white. Pressed, a thin vermilion ribbon drops out of its bottom edge like the ribbon sewn into the spine of a hardback: it unrolls, swings on its stitch and settles hanging straight, while Saved lands, wide and light first, then narrow and heavy. Pressed again, the ribbon is pulled back up into the pill.
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 bookmark ribbon button</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the bookmark ribbon button: the Save for later of a reading app, a thin ink pill on white. Pressed, a thin
vermilion ribbon drops out of the bottom edge of the pill like the ribbon of a hardback, swings on its
stitch and settles; the words become Saved. Pressed again, the ribbon is pulled back up into the pill -->
<div class="mark" data-mark>
<div class="mark__wrap">
<button class="mark__btn" type="button" aria-pressed="false" data-btn data-autoclic>
<span class="mark__words">
<span class="mark__word" data-word="rest">Save for later</span>
<span class="mark__word mark__word--done" data-word="done" aria-hidden="true">Saved</span>
</span>
</button>
<svg class="mark__ribbon" aria-hidden="true" data-ribbon>
<path class="mark__shade" data-shade></path>
<path class="mark__tape" data-tape></path>
<path class="mark__stitch" data-stitch></path>
</svg>
</div>
<p class="mark__note" data-note>Twelve minutes of reading</p>
<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.
18 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 bookmark ribbon button” from Animorio and fit it into my siteThe ribbon has three numbers: its length, its angle and how fast the angle changes. Every frame a spring pulls the angle back to zero (STIFF) and damping eats its speed (DAMP), with the time step capped at 1/30 of a second. When angle and speed are almost zero and no tween is moving the ribbon, the loop takes itself off GSAP’s ticker and nothing is drawn any more.
The ribbon is a centre line of ten pieces: each piece has the pendulum’s angle plus a bend that grows with the square of the distance from the stitch, against the speed. So while it swings the lower part lags behind and the ribbon curves like cloth. The two edges sit half a width either side of the line, and at the end the swallowtail cut goes back NOTCH pixels.
GSAP moves the length: it falls on an In curve to four pixels past LENGTH, and the moment it stops the pendulum gets a kick of speed; then the length springs back to LENGTH on an elastic. Pulling it up, the kick goes the other way and the length goes back to zero on a harder In curve.
Archivo has width and weight axes. The old words leave narrowing and rising; the new ones start 116 wide and 320 heavy, rise from below to a pixel under their place and settle, while width and weight go to 92 and 640.
It is a real button with aria-pressed, which says whether the piece is saved; an aria-live line announces the new state; keyboard focus has its ring set off the edge. The ribbon is a decorative SVG with aria-hidden.
| Name | File | Default | What it changes |
|---|---|---|---|
LABEL | script.js | Save for later | The words at rest. |
DONE | script.js | Saved | The words once the piece is saved. |
NOTE | script.js | Twelve minutes of reading | The small line under the pill. |
NOTE_DONE | script.js | In your reading list | The small line once the piece is saved. |
LENGTH | script.js | 46 | How far the ribbon hangs below the pill, in pixels. |
WIDTH | script.js | 12 | The width of the ribbon, in pixels. |
SWING | script.js | 1 | How hard the ribbon swings when it drops; 0 keeps it still. |
STIFF | script.js | 34 | The spring that brings the ribbon back to hanging straight. |
DAMP | script.js | 3.2 | How quickly the swinging dies away. |
For a Save button that should feel like a gesture: saving an article for later on a blog or a magazine, putting a book on a reading list, bookmarking a recipe.
The pill narrows to 32 pixels from the edges and the ribbon finds its place on the real width. A tap presses the pill like a click; the tip peeking on hover and the ribbon swaying with the mouse are only there with a mouse.
No swinging and no bounces: the ribbon fades in and out already at its length, and the words swap with a fade.
GSAP 3.15.0, Archivo (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 5.0 KB; the libraries: GSAP 28 KB.
Tested on 22 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.