
The slide-to-confirm button
Buttons, Physics, Transitions
NewFree
The Submit request button sends the request by pneumatic post, the way offices did a century ago: a brass carrier with the rolled up sheet comes out of the button, is sucked into the glass tube with a jolt, speeds up, leans and squashes in the bends and vanishes at the top; the glass shakes, a puff of air comes out, and a little later a smaller carrier drops back down with Received.
A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React tab holds 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 pneumatic tube button</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/instrument-sans@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the pneumatic tube: a glass tube rises from the top of the Submit button, bends and climbs out of sight.
Pressed, a brass carrier with the rolled request inside comes up out of the button, is sucked into the tube and
shoots away; a little later a smaller carrier comes back down with the receipt -->
<div class="pneu" data-pneu>
<!-- the line the carrier follows, read with getPointAtLength; the glass is drawn along it in the canvas -->
<svg class="pneu__route" viewBox="0 0 340 480" aria-hidden="true" focusable="false">
<path data-route d="M170 432 L170 316 C170 274 180 258 214 241 L258 219 C292 202 296 186 296 150 L296 -30"/>
</svg>
<canvas class="pneu__canvas" aria-hidden="true" data-canvas></canvas>
<button class="pneu__btn" type="button" aria-describedby="pneu-live" data-btn data-autoclic>
<span class="pneu__words" aria-hidden="true">
<span class="pneu__word" data-word>
<svg class="pneu__icon" viewBox="0 0 24 24" data-icon="send"><path d="M12 19V6.5M6.5 11.5L12 6l5.5 5.5"/><path d="M5 3.5h14"/></svg>
<span data-text>Submit request</span>
</span>
</span>
<span class="sr" data-name>Submit request</span>
</button>
<p class="sr" id="pneu-live" aria-live="polite" data-live></p>
</div>
</main>
<!-- the tick drawn on Received -->
<template data-tick>
<svg class="pneu__icon pneu__icon--tick" viewBox="0 0 24 24"><path d="M5 12.5l4.4 4.4L19 7.4"/></svg>
</template>
<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.
23 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 pneumatic tube button” from Animorio and fit it into my siteThe tube is a hidden SVG path (two Bézier curves between straight runs). At start getTotalLength and getPointAtLength read it every 2 units into a table: position, tangent angle and curvature (how fast the angle turns per unit of length). During the animation the carrier never calls the SVG again: it interpolates in the table, and before the start or past the end it carries on straight.
The carrier has a single coordinate, the length travelled along the tube, moved in steps of 1/240 of a second (with the frame step capped at 1/30): acceleration = (JOLT·e^(−t/0.09) + 2300)·SUCTION − FRICTION·v. The first jolt fades in a tenth of a second, the steady pull stays, friction grows with speed: it leaves at once and then runs towards a terminal speed. Coming back it falls held by the air, and at the bottom a damped spring (the cushion of air in the closed tube) stops it with a bounce.
The carrier's angle chases the tangent through a lightly damped spring, limited to ±0.32 radians: it enters a bend late and swings out of it. The centripetal force v²·k throws it against the outer wall and squashes it along its axis; acceleration stretches it. Stretch and squash go through another spring, so the carrier wobbles after each bend; above a certain speed a soft smear appears behind it.
The tube is drawn in canvas as several strokes parallel to the centre line, offset along the normal: the veiled far wall, the carrier, then the two dark edges where the glass is thickest and two offset white highlights. The shadow on the page is drawn once with shadowBlur into a separate canvas. The shaking moves the points along the normal with a wave that climbs and dies away, none near the collar; at the top a destination-out gradient fades tube and carrier out.
Sending…, Request sent and Received roll through the button like a drum, the tick draws itself with stroke-dashoffset. While the request travels the button has aria-disabled and clicks are ignored; an aria-live region tells each step. The canvas is redrawn only while something moves.
| Name | File | Default | What it changes |
|---|---|---|---|
SUCTION | script.js | 1 | How hard the tube pulls: 1 is the normal pull, 2 twice as hard. |
JOLT | script.js | 9000 | The kick of the first instant of the suction, in pixels a second squared. |
FRICTION | script.js | 1.3 | How much the air and the walls hold the carrier back. |
LEAN | script.js | 1 | How much the carrier leans into the bends (0 = it follows the tube exactly). |
SQUASH | script.js | 1 | How much the carrier stretches when it speeds up and squashes in the bends. |
SHAKE | script.js | 1 | How much the glass shakes when the carrier shoots through. |
BACK | script.js | 1.3 | The seconds before the receipt comes back down the tube. |
HOLD | script.js | 2.4 | The seconds Received stays before the button is ready again. |
For sending a form that matters: a quote request, a support ticket, an application, an order to an office. It suits a site with a crafted or archival character, where the wait for a submission becomes a small show and the receipt coming back says it arrived.
The component is 340 pixels wide and fits a 390 pixel screen; below 370 it narrows to 300 and the drawing of the tube scales with it. The canvas follows the screen's density (up to 2). A pressing finger makes the carrier peek out like the mouse over it.
No journey through the tube: the words fade to Sending…, then to Request sent, and the small carrier fades in already resting in the mouth with Received; then it fades away. The tube does not shake and there are no puffs.
GSAP 3.15.0, Instrument Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 10 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.