Skip to content
animorio
IT
All components

The pneumatic tube button

Buttons, Physics, TransitionsCanvas, GSAPAdvancedCompressed weight: Demo 10 KBGSAP 28 KBFreeDownload the zip

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.

The code

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>

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.

23 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 pneumatic tube button” from Animorio and fit it into my site

What it does

On a white background a midnight blue pill button with an arrow and Submit request; from its top edge, inside a brass collar, a clear glass tube rises with white highlights and bluish edges, bends to the right, climbs on a diagonal held by two brass clamps, straightens and fades out at the top, with its soft shadow on the page. With the mouse over the button (or a finger pressing it) the carrier peeks out of the collar. Clicking, the words become Sending…, the brass carrier with two red felt rings and a little window showing the rolled up sheet rises out of the button into the tube with a small bounce, trembles for an instant while the pressure builds and is then sucked away with a jolt: it speeds up, stretches with the acceleration, leans, squashes and hits the outer wall in the bends, leaves a brass coloured smear and vanishes at the top. The tube shakes with a wave that runs upwards and a little cloud of air comes out of the mouth; the words become Request sent. A little over a second later a smaller carrier drops down the tube, is braked by the cushion of air at the bottom, bounces and settles in the mouth with another puff; the words become Received with a green tick that draws itself. After a couple of seconds the carrier goes back into the button and it says Submit request again.

How it works

The route read once

The 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 law of pressure

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.

Lean, wall and squash

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 glass drawn along the line

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.

The states

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.

Values to change

NameFileDefaultWhat it changes
SUCTIONscript.js1How hard the tube pulls: 1 is the normal pull, 2 twice as hard.
JOLTscript.js9000The kick of the first instant of the suction, in pixels a second squared.
FRICTIONscript.js1.3How much the air and the walls hold the carrier back.
LEANscript.js1How much the carrier leans into the bends (0 = it follows the tube exactly).
SQUASHscript.js1How much the carrier stretches when it speeds up and squashes in the bends.
SHAKEscript.js1How much the glass shakes when the carrier shoots through.
BACKscript.js1.3The seconds before the receipt comes back down the tube.
HOLDscript.js2.4The seconds Received stays before the button is ready again.

Try them in the Playground

When to use it

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.

On phones

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.

With reduced motion

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.

Pitfalls

  • With the tube's shadow drawn every frame with shadowBlur, in the browser without a GPU every frame cost tens of milliseconds: the shadow does not shake, so it is drawn once into a separate canvas and copied.
  • The carrier that bounced at the bottom stayed squashed and turned as at the physics' last step: settling, its stretch, angle and offset from the wall are reset.
  • Rolling the words with fromTo inside a timeline moved the old words down at once (immediateRender): the words jumped before leaving. It needs immediateRender: false.
  • The cushion of air on the way back, made as a growing force, made the carrier oscillate without ever stopping exactly: now it is a damped spring towards the resting point, and after three seconds the carrier settles anyway.
  • With a slow frame's whole time step the jolt made the carrier jump past the first bend: the physics runs in steps of 1/240 of a second, with the frame capped at 1/30.

Libraries

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.

Testing

Tested on 5 October 2026

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

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