
The needle gauge
Counters, Physics, SVG
NewFree
A strip of tabs like the top of a browser, with no brand: drag them and the others step aside on springs, the one in your hand leans with its speed while its curved feet stay planted on the bar. The plus opens a tab that grows out of the button, the cross closes one that shrinks away, and with many tabs only the icons are left.
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 browser tabs</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/geist@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- a strip of tabs like a browser's: drag them and the others move aside on springs, the dragged one leans with
its speed; the active tab is one SVG shape with curved feet that melts into the bar underneath -->
<div class="browser" data-browser>
<div class="strip" data-strip>
<svg class="sheet" aria-hidden="true" data-sheet><path class="sheet__shade" data-shade/><path class="sheet__body" data-body/><path class="sheet__edge" data-edge/></svg>
<div class="tabs" role="tablist" aria-label="Open tabs" aria-describedby="tabs-help" data-tabs>
<div class="tab" role="tab" data-icon="mail" data-autoclic>Inbox</div>
<div class="tab" role="tab" data-icon="road" data-active>Q3 roadmap</div>
<div class="tab" role="tab" data-icon="plane" data-autoclic>Flight to Lisbon</div>
<div class="tab" role="tab" data-icon="pot">Weeknight recipes</div>
<div class="tab" role="tab" data-icon="notes" data-autoclic>Design review notes</div>
<div class="tab" role="tab" data-icon="peak">Trail maps</div>
</div>
<button class="plus" type="button" aria-label="New tab" data-plus>
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 3v10M3 8h10"/></svg>
</button>
</div>
<!-- the bar under the active tab: the same colour, so the tab and the bar are one piece -->
<div class="bar" role="tabpanel" data-panel>
<div class="bar__nav" aria-hidden="true">
<svg viewBox="0 0 16 16"><path d="M10 3.5L5.5 8l4.5 4.5"/></svg>
<svg viewBox="0 0 16 16"><path d="M6 3.5L10.5 8 6 12.5"/></svg>
<svg viewBox="0 0 16 16"><path d="M12.6 8.6a4.7 4.7 0 1 1-1.5-4.1"/><path d="M11.6 1.9v2.9H8.7"/></svg>
</div>
<div class="bar__address">
<span class="bar__icon" data-bar-icon></span>
<span class="bar__title" aria-live="polite" data-bar-title></span>
</div>
</div>
<p class="sr" id="tabs-help">Arrow keys switch tabs, Shift with an arrow moves a tab, Delete closes it.</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.
26 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 browser tabs” from Animorio and fit it into my siteThe room between the two ends, minus the plus, is shared among the open tabs within TAB_WIDTH; when they get narrow, the active tab keeps ACTIVE_MIN and the others share the rest. So every tab has a target x and width, and the plus sits right after the last one. After a close with the mouse the widths may shrink but not grow, until the pointer leaves the strip.
Every tab’s x and width, the plus’s x and the two edges of the active shape are numbers with a speed, pulled towards their goal by a damped spring (SPRING) in a requestAnimationFrame loop, with the step capped at 1/30 of a second. The loop switches off when everything is still. A new tab starts zero wide at the plus’s x; a closed one has a target width of zero and leaves the page when it gets there.
The active tab is an SVG path rewritten every frame from its two edges: from the left foot (a quarter circle of radius FOOT curving into the bar) up the side, round the top corners, down the other side and out along the right foot. When the tab changes, the edge on the side it is going uses the full spring and the other a softer one (TRAIL), so the shape stretches and then gathers.
With pointer events and setPointerCapture the tab you take follows the hand; its speed, filtered, gives the lean (LEAN degrees per pixel a second, up to MAX_LEAN), on a spring of its own that brings it upright. The content rotates around its base and in the shape only the top corners move: the feet stay on the bar. The tab’s place is the number of other tabs whose middle it has passed; let go, it drops into its place with the hand’s speed.
The strip is a tablist with a single element reachable with Tab (roving tabindex): the arrows switch tabs, Home and End go to the ends, Shift and an arrow move the tab one place, Delete closes it. The bar below is the tabpanel, labelled by the active tab, and the order of the elements in the page follows the order on screen after every move.
| Name | File | Default | What it changes |
|---|---|---|---|
SPRING | script.js | [320, 0.72] | The tabs going to their place: the strength of the spring and its damping (1 = no bounce). |
TRAIL | script.js | 0.42 | How much softer the spring of the active shape’s back edge is: lower, it stretches more. |
LEAN | script.js | 0.011 | The degrees a dragged tab leans for every pixel a second of speed. |
MAX_LEAN | script.js | 8 | The most a dragged tab leans, in degrees. |
TAB_WIDTH | script.js | [38, 224] | The narrowest and the widest a tab may be, in pixels. |
ACTIVE_MIN | script.js | 118 | The width the active tab keeps while there is room, so its title shows. |
FOOT | script.js | 9 | The radius of the active tab’s curved feet, in pixels. |
NEW_TITLE | script.js | 'New tab' | The title of a tab opened with the plus. |
For an app with several documents open at once (an editor, a mail client, a design tool, a terminal in the browser), for the files of an IDE, or for a page that shows a product dressed as a browser. From two to about fifteen tabs.
At 390 pixels the tabs are narrow: the active tab keeps its title and cross, the others show only their icon. They drag with a finger (touch-action: pan-y, so a vertical finger still scrolls the page), a tap chooses one, and the plus opens a tab while there is room for one more icon. Nothing spills over sideways.
No springs and no lean: the tabs go straight to their place, the active shape jumps to the new tab, the dragged one follows the hand and settles when let go; a closed tab disappears and a new one appears already wide. The title in the bar changes with a fade.
GSAP 3.15.0, Geist (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 9.3 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.