
The slide-to-confirm button
Buttons, Physics, Transitions
NewFree
The offline download of an audiobook app, made as a shelf between two brass bookends: every audiobook is a book. The one downloading appears in glass, tips, falls and leans on the row, and its cloth rises from the bottom with the bytes; downloaded, it stands up and slides into place with a little knock. If the connection drops the book slips down like a ladder down a wall; at the end a finger runs along the spines, a light passes over the gold and the bookcase lights up.
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 filling shelf</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/standard.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the offline downloads of an audiobook app, as a shelf between two brass bookends: every audiobook is a book.
The one downloading appears as a glass ghost, tips over and leans on the row, and fills with its cloth from
the bottom as its bytes arrive; done, it stands up and slides into the row. The books are built by script.js
from the list at the top of it -->
<section class="shelf" aria-labelledby="shelf-title" data-shelf>
<div class="shelf__head">
<div class="shelf__status">
<p class="shelf__phase"><span class="shelf__swap" data-phase>Not on this phone yet</span></p>
<p class="shelf__note"><span class="shelf__swap" data-note>About 10 s on this Wi-Fi</span></p>
</div>
<p class="shelf__big" aria-hidden="true">
<span class="shelf__swap" data-big>1.4 GB</span>
<svg class="shelf__tick" viewBox="0 0 28 28" data-tick><path d="M6.5 14.8l5 5 10-11.4" pathLength="1"/></svg>
</p>
</div>
<div class="shelf__case" role="progressbar" aria-labelledby="shelf-title" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" aria-valuetext="Not downloaded" data-bar>
<div class="shelf__light" aria-hidden="true" data-light></div>
<div class="shelf__row" aria-hidden="true" data-row>
<span class="bookend bookend--start"></span>
<span class="bookend bookend--end" data-end></span>
</div>
<div class="shelf__board" aria-hidden="true"></div>
</div>
<div class="shelf__foot">
<div class="shelf__list">
<h2 class="shelf__title" id="shelf-title">For the flight</h2>
<p class="shelf__meta" data-meta>10 audiobooks · 22 h</p>
</div>
<div class="shelf__buttons">
<button class="pill pill--icon" type="button" aria-label="Cancel download" data-cancel hidden>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7.5 7.5l9 9M16.5 7.5l-9 9"/></svg>
</button>
<button class="pill" type="button" data-action data-autoclic><span class="pill__label" data-label>Download</span></button>
</div>
</div>
<p class="sr" role="status" aria-live="polite" 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.
67 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 filling shelf” from Animorio and fit it into my siteA book is a rigid rectangle t thick and h tall that turns about the corner it stands on. Its weight gives it an angular acceleration θ'' = 3g(h·sin θ − t·cos θ) ÷ 2(t² + h²), negative under the balance angle atan(t/h) (the book goes back upright) and positive above it (it falls). That is why the book is set down half a degree past its balance, with the smallest push: it hesitates, speeds up and knocks. The sums run on GSAP’s ticker in four substeps, with the frame limited to 1/30 of a second; at the knock the speed turns round and shrinks by BOUNCE.
With its foot a distance g from the row, the angle the book leans at comes from the contact: either its face touches the top corner of the book before (tan θ = g ÷ H, if the book is tall enough to reach it), or its top corner touches that book’s side (sin θ = g ÷ h). Whichever comes first. The same function serves while it leans, while it stands up sliding towards the row (g goes to zero and the angle with it) and while it slips away in the error: the foot moves right until the book is at 64 degrees or touches the bookend, just like a ladder sliding down a wall. So a book never goes through another one.
The book downloading leans at LEAN degrees, but always at least 7 degrees past its balance (a thick book has to lean further not to stand back up); if there is no room between the row and the bookend to lean like that, it stands upright next to the row, and the last one drops straight into its slot. Standing up it knocks against its neighbour with the speed it had and rocks on its other corner: the pivot changes, gravity brings it back, the neighbour stops it and takes some of its swing at every knock, and past 0.6 of its balance it is held back.
The pretend download is a timeline of arrivals of random size and interval with a stall or two; the books follow the bytes with a delay of FOLLOW seconds, but close the last stretch at a steady pace, otherwise an exponential chase would stay at 99%. The cloth is a layer over the glass cut from the bottom with clip-path: inset(), whose cut reaches 12 pixels past the sides to let its shadow through. If the books fall behind (each has almost a second of motion of its own) they hurry by 1.6 or 2.2 times, and an error only shows once the book that did not arrive is down on the shelf.
The spines are divs with writing-mode vertical-rl; a title too long for its spine first closes up a little (the sans ones on the wdth axis of Bricolage Grotesque), then, on a thick book, goes on two lines cut at the space nearest the middle if the halves are balanced, and only last gets smaller. The gold is a gradient with background-clip: text. At the end the same band of light crosses every book: each spine has its own copy of the gradient shifted by where the book stands (--bx), so it reads as one light; then the wall behind the books lights up with a warm gradient that spreads from the middle.
| Name | File | Default | What it changes |
|---|---|---|---|
SECONDS | script.js | 8 | How long the download takes in the demo, in seconds (a real one follows its bytes). |
BOOKS | script.js | [['Salt Road', 142], ['Night Ferry', 96], …] | The audiobooks from the left: the title on the spine and the size in megabytes, which also makes the book’s thickness. From 2 to 14. |
FAIL_EVERY | script.js | 2 | In the demo, one download in how many loses its connection (0 never). |
FAIL_AT | script.js | 0.58 | How far the download that goes wrong gets, from 0 to 1. |
STALLS | script.js | 1 | How often the connection stalls for a moment: 0 never, 1 now and then, 2 often. |
LEAN | script.js | 30 | How far the book downloading leans on the row, in degrees (always at least 7 past the point where it would stand back up). |
GRAVITY | script.js | 3400 | How hard the books fall over, in pixels a second squared. |
BOUNCE | script.js | 0.32 | How much a book bounces off the one it lands on: 0 not at all, 0.6 a lot. |
FOLLOW | script.js | 0.25 | How long the books take to catch up with the bytes, in seconds. |
For an audiobook, ebook, podcast or music app that downloads a list to listen to or read offline, for a digital library or a comics app, and in general for a queued download of several items that have a name, where it is worth seeing which ones have already arrived.
The card is 400 pixels wide and on a phone takes the width of the screen less 16 pixels a side (358 at 390): the books get thinner to fill the shelf and the titles fit their spines again. The buttons are 44 tall and work by touch; the pill that swells under the pointer is only there with a mouse. Below 390 pixels a very long error message ends in an ellipsis.
The books appear already upright in their place and the cloth fades in as the bytes arrive: no falling, no rocking, no slipping in the error (the book’s glass turns red). At the end no finger on the spines and no band of light: the bookcase light comes on with a fade and the tick appears. The words change with a fade.
GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0, Fraunces (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 17 KB; the libraries: GSAP 28 KB.
Tested on 7 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.