
The water button
Buttons, SVG, Physics, Hover
NewFree
The bottom bar of a yarn shop's app, drawn by a single thread: the house, the page, the heart, the bag and the person are all the same yarn rising out of its line and going back to it. A stretch of the thread is dyed madder red and sits in the icon of the tab you are on. Choosing another, the whole yarn slides along its own path, like a thread pulled through a sewing machine: the twist of the wool runs along the line, the red stretch climbs over the icons it meets, each one pulling tight as it passes, and settles in the new one a pixel too far.
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 single thread tab bar</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/instrument-sans@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the tab bar of a yarn shop's app. One woollen thread runs across the whole bar and draws every icon on the
way, rising out of its own line and coming back to it. A stretch of the thread is dyed madder red, and it sits
in the icon of the tab you are on. Choosing another tab, the whole yarn slides along its own path like a
thread pulled through a sewing machine: the red stretch runs along the line, climbs over every icon in
between, and settles in the new one, which pulls itself tight. The thread is one SVG path rebuilt every frame -->
<nav class="bar" aria-label="Main" data-bar>
<svg class="yarn" aria-hidden="true" data-yarn>
<path class="yarn__shade" data-shade/>
<path class="yarn__thread" data-thread/>
<path class="yarn__twist" data-twist/>
<path class="yarn__dye" data-dye/>
<path class="yarn__dye-twist" data-dye-twist/>
</svg>
<button class="tab" type="button" aria-current="page" data-tab data-icon="M-10 0V-12.5L0-22L10-12.5V0">
<span class="tab__name">Shop</span>
</button>
<button class="tab" type="button" data-tab data-autoclic data-icon="M-8.5 0V-22H1.4L8.5-14.9V0">
<span class="tab__name">Patterns</span>
</button>
<button class="tab" type="button" data-tab data-icon="M0 0C-4.7-4.3-11.3-8.9-11.3-14.6C-11.3-19.1-7.5-22-4.1-21C-2.1-20.5-.7-19.1 0-17.6C.7-19.1 2.1-20.5 4.1-21C7.5-22 11.3-19.1 11.3-14.6C11.3-8.9 4.7-4.3 0 0">
<span class="tab__name">Saved</span>
</button>
<button class="tab" type="button" data-tab data-autoclic data-icon="M-10.5 0L-9.2-13.4H3.9C3.9-20.6-3.9-20.6-3.9-13.4H9.2L10.5 0">
<span class="tab__name">Bag</span>
</button>
<button class="tab" type="button" data-tab data-autoclic data-icon="M-10 0C-9.6-6-5.6-10 0-10A5.6 5.6 0 0 1 0-21.2A5.6 5.6 0 0 1 0-10C5.6-10 9.6-6 10 0">
<span class="tab__name">You</span>
</button>
</nav>
</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.
22 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 single thread tab bar” from Animorio and fit it into my siteThe icons are written in each button's data-icon attribute as an SVG stroke in a little frame of its own: x round the middle, y up from the line (negative), and each one starts on the line and goes back to it. Where a shape cannot close in one go the stroke goes over itself: the bag's handle comes back along the top edge, the heart starts and ends at its tip. At start every stroke goes through getPointAtLength every 0.7 units and becomes a list of points.
Every frame the icons are put at their middles and their sizes (REST_SIZE for the others, 1 for the chosen tab, a little more under the pointer) and sewn into a single polyline with the straight stretches of the line between them; the chosen tab rises LIFT pixels, but the feet of the icons stay on the line. Meanwhile the lengths of the pieces are added up: that is the length of thread from the start, which everything else uses.
The red's position is a number in tabs (2.4 means between Saved and Bag): its middle goes from the middle of one icon, measured along the thread, to the middle of the next, and its length from the length of one to that of the other, plus three pixels each side along the line. The piece of polyline between those two lengths becomes the second path, red and a little thicker. The icons near the red grow with a smooth curve of the distance, so each one pulls tight as the red goes through it.
The twist marks are slanted dashes as wide as the thread, one every TWIST pixels of length, counted from the middle of the red: when the red moves on, they move on with it, and the whole thread seems to slide through its guides. For every mark the script finds the point and the direction at that length; those inside the red go into the dark red path, the others into the brown one.
Choosing a tab, a GSAP tween takes the point the pull is heading for there, with a power2.inOut that lasts longer for far tabs; the red's position follows it on a damped spring (SPRING), in steps of 1/120 of a second and never more than 1/30 a frame, and gets there a touch too far before settling. When nothing moves the loop stops and the drawing stays as it is.
| Name | File | Default | What it changes |
|---|---|---|---|
START | script.js | 0 | The tab chosen when the page opens (0 is the first one). |
TRAVEL | script.js | 0.34 | The seconds the red stretch takes to run from one tab to the next. |
SPRING | script.js | [240, 23] | The spring the thread settles on: how stiff, and how much it is held back (less, further it overshoots). |
REST_SIZE | script.js | 0.78 | How big the icons of the other tabs are, against the chosen one. |
HOVER_SIZE | script.js | 0.1 | How much an icon under the pointer or the finger pulls itself tighter. |
LIFT | script.js | 2.5 | How many pixels the chosen icon rises from the line. |
TWIST | script.js | 3.2 | The space between two twists of the yarn, in pixels. |
For the bottom bar of a handmade shop's app or of anyone who works with thread: wool, haberdashery, tailoring, embroidery, fabrics, a knitting workshop. It works with four or five tabs with simple icons that can be drawn in one stroke; to change them just rewrite data-icon with a stroke that starts on the line and goes back to it. The colours of the thread, the red and the bar are in the variables.
The bar is at most 420 pixels wide and on a phone it narrows to the screen with a 16 pixel margin each side (358 on a 390 phone): the icons keep their size and the thread between them gets shorter. It stays 68 tall and every tab is a target of about 69 by 68 pixels. With a finger down on a tab its icon pulls tight as with the mouse over it, and the tap chooses it.
The thread does not slide and the icons do not pull tight: choosing another tab the drawing is set again at once with the red in the new icon, in a short fade. When the page opens the thread fades in, already in place.
GSAP 3.15.0, Instrument Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 6.9 KB; the libraries: GSAP 28 KB.
Tested on 19 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.