Skip to content
animorio
IT
All components

The rubber band tab bar

Navigation, Physics, SVGCanvas, GSAPAdvancedCompressed weight: Demo 14 KBGSAP 28 KBFreeDownload the zip

The bottom bar of a home workout app: the chosen tab stands up like a peg with an orange elastic cord round it. Choosing another, the cord is pulled over there, hooked on the new peg and let go off the old one: it snaps across, tightens round it and wobbles, and the icons feel the tug.

The code

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 rubber band tab bar</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/funnel-sans@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the tab bar of a home workout app. The tab you are on stands up from the bar like a peg, with a rubber band
         round it. Choosing another tab, the band is pulled across the bar, hooked round the new peg and let go off
         the old one: it snaps over and wobbles. The band is a ring of springs worked out on every frame and drawn
         in the canvas over the tabs; the pegs and the icons below feel it -->
    <nav class="bar" aria-label="Main" data-bar>
      <button class="tab" type="button" data-tab="train" data-autoclic>
        <span class="tab__cap" data-cap>
          <span class="tab__peg" aria-hidden="true" data-peg></span>
          <svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true">
            <g data-part="bell"><path d="M7.7 12C6.5 9.4 7.2 5 12 5s5.5 4.4 4.3 7"/><path d="M8.6 20.6a6.2 6.2 0 1 1 6.8 0z"/></g>
          </svg>
        </span>
        <span class="tab__label">Train</span>
      </button>
      <button class="tab" type="button" data-tab="timer">
        <span class="tab__cap" data-cap>
          <span class="tab__peg" aria-hidden="true" data-peg></span>
          <svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true">
            <path d="M12 6.5a7 7 0 1 0 0 14a7 7 0 1 0 0-14zM10 3.4h4M12 3.4v3.1M17.6 7.4l1.3-1.3"/>
            <path d="M12 13.5V10" data-part="hand"/>
          </svg>
        </span>
        <span class="tab__label">Timer</span>
      </button>
      <button class="tab" type="button" data-tab="today">
        <span class="tab__cap" data-cap>
          <span class="tab__peg" aria-hidden="true" data-peg></span>
          <svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true">
            <defs><clipPath id="sky"><rect x="0" y="0" width="24" height="16.4"/></clipPath></defs>
            <g clip-path="url(#sky)"><g data-part="sun"><path d="M7 17a5 5 0 0 1 10 0"/><path d="M12 7.6V9.4M6.2 10.4l1.25 1.25M17.8 10.4l-1.25 1.25"/></g></g>
            <path d="M3.5 17h17M8.5 20.2h7"/>
          </svg>
        </span>
        <span class="tab__label">Today</span>
      </button>
      <button class="tab" type="button" data-tab="stats" data-autoclic>
        <span class="tab__cap" data-cap>
          <span class="tab__peg" aria-hidden="true" data-peg></span>
          <svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true">
            <path d="M6.5 19.5v-5" data-part="bar"/><path d="M12 19.5v-9" data-part="bar"/><path d="M17.5 19.5v-14" data-part="bar"/>
          </svg>
        </span>
        <span class="tab__label">Stats</span>
      </button>
      <button class="tab" type="button" data-tab="you" data-autoclic>
        <span class="tab__cap" data-cap>
          <span class="tab__peg" aria-hidden="true" data-peg></span>
          <svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true">
            <path d="M12 4.6a3.6 3.6 0 1 0 0 7.2a3.6 3.6 0 1 0 0-7.2z" data-part="head"/>
            <path d="M5 20c.8-3.6 3.6-5.6 7-5.6s6.2 2 7 5.6"/>
          </svg>
        </span>
        <span class="tab__label">You</span>
      </button>
      <canvas class="bar__band" aria-hidden="true" data-band></canvas>
    </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>

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.

22 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 rubber band tab bar” from Animorio and fit it into my site

What it does

On a white background, alone in the middle, a putty-coloured bar like a rubber mat, with five tabs: a kettlebell, a stopwatch, a rising sun, three bars and a person, named Train, Timer, Today, Stats and You. Today, in the middle, stands up like a white peg with its icon in black, and round it there is an orange elastic cord with a woven sheath of slanted stripes and a small metal clasp that closes it. On arrival all five pegs are up with the cord stretched round them all, pale from being pulled so hard; then the pegs drop in pairs from both ends and the cord closes in on Today. Choosing another tab, the cord is taken on the side facing it and pulled past the tab, as if by a hand: it stretches into a thin drop, paler and finer where it is pulled hardest, with the stripes of the sheath spreading apart. Past the tab, its peg stands up inside the cord, which wraps it with one strand over and one under; the hand lets go and for a moment the cord hugs both pegs. Then the old peg drops and the cord snaps over to the new one, tightens round it and wobbles, and the metal clasp rides along with the rubber. The pegs feel the pull: the new one leans towards the old one while the cord tugs it, and its icon does its little move: the sun rises, the kettlebell swings on its handle, the stopwatch hand goes round, the bars bounce one after the other, the head nods. With the mouse over a tab the cord leans slightly towards it; the chosen tab, clicked again, plucks the cord. The cord can also be taken with a finger or the mouse and pulled: let go near another tab it goes there, let go near its own peg it springs back like a slingshot.

How it works

A ring of springs

The cord is a closed ring of 144 points tied by springs, plus a bending stiffness (the fourth difference of the positions) that rounds its bends without lifting it off the peg's corners. A second damping, on the fourth difference of the speeds, only kills tight wrinkles: after the snap the rubber wobbles but does not crumple. The sums are done in steps of half a millisecond, with the frame step capped at 1/30 of a second.

The pegs and the hand

The pegs that stand up are rounded boxes: a point that gets inside is put back on the edge and loses the speed that took it in. The sum of those speeds, divided by the time, is the cord's force on the peg: the peg shifts and leans with it on a spring of its own, a few pixels at most. The hand is the point of the cord facing the new tab most: a GSAP tween carries it past the tab, and within each frame it moves a little at every step of the sums.

The guided wrap

When the new peg stands up, the two strands of the drop pass it almost on its middle line: pushed point by point, they would be put out sideways and one strand would stay across the peg. So, while it rises, each point is only pushed out on its own side: over the top for one half of the cord (from the hand onwards), under the bottom for the other. Then the hand lets go, the old peg drops and the tension does the rest.

Rubber that stretches

Each piece of the cord is drawn thinner and paler the more it is stretched from rest, like real rubber. The slanted stripes of the sheath sit on the cord's own points, so they spread apart where it stretches; the metal clasp sits on the first point and turns with it. To keep it cheap the pieces are grouped by colour and thickness: each group is one path, and at rest the whole cord is three.

Still means no drawing

When no point moves any more, the hand is free and the springs of the pegs and the icons are still, the loop stops working and drawing until something happens (a click, the mouse, a finger). The tabs are buttons in a nav, the chosen one has aria-current; the arrows, Home and End move the focus, Enter and Space choose. The library's preview clicks Train, Stats and You in turn: they have data-autoclic.

Values to change

NameFileDefaultWhat it changes
STARTscript.js2The tab chosen when the page opens (0 is the first, 2 the middle one).
PULLscript.js0.24The seconds it takes to pull the cord to the next tab (a little more to a far one).
TENSIONscript.js1How hard the rubber pulls back: more, a faster and sharper snap.
SNUGscript.js0.78How long the cord is at rest, against the peg it hugs: less, a tighter cord.
WOBBLEscript.js0.55How long the cord wobbles after a snap (0 stops at once, 1 very lively).
THICKscript.js4.6The thickness of the cord, in pixels.
PALEscript.js0.8How pale the rubber goes where it is stretched hard.
LEANscript.js1How far the cord leans towards the tab under the pointer (0 not at all).

Try them in the Playground

When to use it

For the navigation bar of an app made for phones, with four or five sections, when changing section can be a little playful: a workout app, an app for children, a creative tool. Even standing still, the cord round the peg says at once where you are.

On phones

The bar is at most 420 pixels wide and on a phone narrows to the screen, with a 16 pixel margin each side (358 on a 390 phone); it stays 70 tall and each tab is a 68 by 70 pixel target. With a finger down on a tab the cord leans towards it, as with the mouse over it; from the chosen peg the cord can be taken and dragged with a finger.

With reduced motion

The cord does not stretch or snap: it fades off the old peg and back in already tight round the new one, while the pegs fade in and out without rising. No entrance, no icon moves, no plucking and no dragging.

Pitfalls

  • The first band, a blue line all round the peg, looked like the keyboard focus ring when still: seen from above a band really is just a line. It became an orange cord with a sheath of slanted stripes and a metal clasp, which reads as elastic even standing still.
  • Raising the new peg inside the drop, the two strands passed it almost on its middle line: the point by point collision pushed them out sideways, one strand stayed across the peg and the band slipped off and lay coiled on the bar. Now, while the peg rises, each half of the cord is only pushed out on its own side.
  • After the snap the points arriving with momentum piled up against the peg and the cord kept a zigzag kink. It took a much higher bending stiffness (stable only with a half millisecond step), a resistance to squeezing equal to the one to stretching and a damping that only acts on tight wrinkles.
  • The first snap was soft and a change took over a second, because the hook crept over its last pixels before letting go: now the hand lets go as soon as the new peg is up, and the rubber is stiffer.
  • The pull towards the tab under the mouse was fifteen times too strong: the band stretched all the way to the next tab, as if it were already chosen. Now it leans out a few pixels.
  • The kettlebell, this small, with a narrow round handle looked like a padlock: the handle is now wide and grows out of the body's shoulders.
  • The ResizeObserver fires as soon as it is switched on: it put the band back round the chosen tab and wiped out the entrance. Now it only steps in when the bar's size really changes.

Libraries

GSAP 3.15.0, Funnel Sans (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 14 KB; the libraries: GSAP 28 KB.

Testing

Tested on 5 October 2026

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

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