
The slide-to-confirm button
Buttons, Physics, Transitions
NewFree
A list of requests that refreshes when pulled down: it follows the hand on a rubber band that gets harder the further it goes, the rows draw a little apart, a ring fills and past the mark its arrow turns round; let go there, the ring spins, closes into a tick and the rows go back up in a wave while the new request drops into the space.
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>Pull to refresh</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/golos-text@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- pull to refresh: the list pulled down stretches like an elastic and a ring fills; let go past the mark and it
refreshes, then the rows go back up in a wave and the new request drops into the space they leave -->
<section class="inbox" aria-labelledby="inbox-title" data-inbox data-autoclic>
<header class="inbox__head">
<div>
<h2 class="inbox__title" id="inbox-title">Requests</h2>
<p class="inbox__when" data-when>Updated 5 min ago</p>
</div>
<button class="inbox__refresh" type="button" aria-label="Refresh" data-refresh>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M19.5 12a7.5 7.5 0 1 1-2.2-5.3"/><path d="M19.6 4.2v3.6H16"/></svg>
</button>
</header>
<div class="inbox__body">
<div class="ptr" aria-hidden="true" data-ptr>
<svg class="ptr__ring" viewBox="0 0 40 40" data-ring>
<circle class="ptr__track" cx="20" cy="20" r="16"/>
<g data-spin>
<circle class="ptr__arc" cx="20" cy="20" r="16" transform="rotate(-90 20 20)" data-arc/>
</g>
<path class="ptr__arrow" d="M20 13.5v13M15 21.5l5 5 5-5" data-arrow/>
<path class="ptr__check" d="M14 20.5l4 4 8-8.5" data-check/>
</svg>
<span class="ptr__label" data-label>Pull to refresh</span>
</div>
<div class="inbox__scroll" data-scroll>
<ul class="inbox__list" aria-labelledby="inbox-title" data-list></ul>
</div>
</div>
<p class="sr" 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.
25 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 “Pull to refresh” from Animorio and fit it into my siteThe hand’s distance becomes the list’s movement with STRETCH × (1 − e^(−distance ÷ STRETCH)): at first it follows almost the same, then less and less, and it never goes past STRETCH pixels. It is pulled only when the list is at its top, with a finger, the mouse or a trackpad’s wheel (while the wheel keeps going; when it stops, it is like letting go); otherwise the list scrolls as usual.
The first row follows the hand exactly (with the wheel, which moves in jumps, it is on a spring too); each of the others heads for its place on a spring (SPRING), worked out on GSAP’s ticker in steps of 4 milliseconds, each a little softer than the one above (LAG): so, let go, the list goes back up in a wave. The places spread by up to SPREAD pixels a row towards the rubber band’s limit, and a row never goes over the one before it: when it catches up, it is pushed along.
Behind the list, an SVG circle whose arc (stroke-dasharray) is the share of the mark (MARK) reached, while the ring grows from 0.62 to 1; past the mark the arrow turns 180 degrees with back.out and the ring gives a hop. While refreshing, the arc spins, lengthening and shortening in a loop; at the end it closes, finishes its turn instead of stopping where it is, and the tick draws inside it with stroke-dashoffset.
Added at the top, it moves the others down in the layout: their springs take that move off at once, so they stay where they were and then slide down by themselves; the new one starts above, hidden under the head, and drops into place. A moment later it loses its edge and the label New. There are eight requests at most: when one arrives, the oldest goes.
The round button refreshes with the keys too (first it takes the list to the top, if it had scrolled); the list has aria-busy while it refreshes, and an aria-live region says Refreshing and then who the new request came from. A click from a script (the library’s preview) is a pretend finger: it pulls the list past the mark, holds a moment and lets go.
| Name | File | Default | What it changes |
|---|---|---|---|
MARK | script.js | 84 | How far, in pixels, the list must be pulled for letting go to refresh it. |
STRETCH | script.js | 170 | The most the list can be pulled, in pixels: the rubber band’s limit. |
SPREAD | script.js | 10 | How far, in pixels, each row draws away from the one above, towards the limit. |
SPRING | script.js | [380, 0.74] | The rows’ springs: how stiff they are and how much they are held back. |
LAG | script.js | 0.35 | How much softer each spring is than the one above: the wave. |
LOAD | script.js | 1.3 | The seconds a refresh lasts (here a pretend one). |
ARRIVALS | script.js | [['Ines Duarte', 'Table for 2 tonight at 9, if anything is free?'], …] | The requests that arrive with a refresh, one each time. |
For lists that change by themselves and are checked often on a phone: messages, bookings, orders, news, notifications, a notice board.
It is pulled with a finger: on the list at its top a drag downwards does not scroll the page (the touchmove is stopped only then), while in every other case the list scrolls as usual, without bouncing past its edge (overscroll-behavior contain).
The list still follows the hand, but without springs or wave: let go, it goes straight to its place; the ring and the button’s arrow do not spin (the ring is full while refreshing), the tick appears without being drawn and the new request appears already in place.
GSAP 3.15.0, Golos Text (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 8.1 KB; the libraries: GSAP 28 KB.
Tested on 3 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.