Skip to content
animorio
IT
All components

The game piece tab bar

Navigation, Physics, 3DCSS only, GSAPAdvancedCompressed weight: Demo 11 KBGSAP 28 KBFreeDownload the zip

The bottom bar of a board game nights app, in green felt: the chosen tab sits under an ivory game piece with its icon carved in the top. Choosing another, the piece is tossed there, flips over in the air showing the new icon carved on its other face, lands on its rim and rattles like a coin spinning down on a table, faster and faster, until it lies flat.

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 game piece tab bar</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/gabarito@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the tab bar of a board game nights app, in green felt. The tab you are on sits under an ivory game piece with
         its icon carved in the top. Choosing another tab, the piece is tossed there: it flips over in the air and
         shows the new icon, carved on its other face, lands on its rim and rattles down flat like a coin spun on a
         table. The piece is built by the script from the icons below, in layers, and moved in 3D -->
    <nav class="bar" aria-label="Main" data-bar>
      <button class="tab" type="button" data-tab data-autoclic>
        <span class="tab__print" data-print><svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true" data-icon>
          <path d="M12 3.6c1.55 0 2.75 1.2 2.75 2.7 0 .85-.4 1.6-1 2.05l4.45 1.6c1.05.4 1.55 1.3 1.25 2.15-.3.8-1.1 1.1-2 .9l-2.1-.4 2.5 5.95c.35.9-.25 1.8-1.25 1.8h-2l-2.6-3.85-2.6 3.85h-2c-1 0-1.6-.9-1.25-1.8l2.5-5.95-2.1.4c-.9.2-1.7-.1-2-.9-.3-.85.2-1.75 1.25-2.15l4.45-1.6c-.6-.45-1-1.2-1-2.05 0-1.5 1.2-2.7 2.75-2.7z"/>
        </svg></span>
        <span class="tab__label">Games</span>
      </button>
      <button class="tab" type="button" data-tab>
        <span class="tab__print" data-print><svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true" data-icon>
          <path d="M18.9 14.3a7.6 7.6 0 1 1-9.3-9.3a6.2 6.2 0 0 0 9.3 9.3z"/>
          <path d="M17.3 3.4l.6 1.5 1.5.6-1.5.6-.6 1.5-.6-1.5-1.5-.6 1.5-.6z"/>
        </svg></span>
        <span class="tab__label">Nights</span>
      </button>
      <button class="tab" type="button" data-tab>
        <span class="tab__print" data-print><svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true" data-icon>
          <path d="M3.6 20.2h16.8M9.2 20.2V11.2h5.6v9M3.6 20.2v-5.4h5.6M20.4 20.2v-4h-5.6"/>
          <path d="M12 3.7l.75 1.7 1.85.2-1.4 1.25.4 1.85L12 7.75l-1.6.95.4-1.85-1.4-1.25 1.85-.2z"/>
        </svg></span>
        <span class="tab__label">Scores</span>
      </button>
      <button class="tab" type="button" data-tab data-autoclic>
        <span class="tab__print" data-print><svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true" data-icon>
          <rect x="4.2" y="15.4" width="15.6" height="4.8" rx="1.2"/>
          <rect x="6.4" y="10.6" width="13" height="4.8" rx="1.2"/>
          <rect x="4.8" y="5.8" width="11.6" height="4.8" rx="1.2"/>
        </svg></span>
        <span class="tab__label">Shelf</span>
      </button>
      <button class="tab" type="button" data-tab data-autoclic>
        <span class="tab__print" data-print><svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true" data-icon>
          <path d="M12 4.2a3.6 3.6 0 1 0 0 7.2a3.6 3.6 0 1 0 0-7.2zM5 19.8c.8-3.6 3.6-5.6 7-5.6s6.2 2 7 5.6"/>
        </svg></span>
        <span class="tab__label">Profile</span>
      </button>
      <!-- the piece's shadow on the felt (clipped to the bar) and the piece itself: both drawn over the tabs, neither
           takes the taps -->
      <span class="bar__shade" aria-hidden="true"><span class="bar__shadow" data-shadow></span></span>
      <span class="piece" aria-hidden="true" data-piece><span class="piece__body" data-body></span></span>
    </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.

21 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 game piece tab bar” from Animorio and fit it into my site

What it does

On a white background, alone in the middle, a dark green felt bar 70 pixels tall, with the fine grain of the cloth and a slightly brighter light at the top left, and five tabs printed on the felt in pale green: a meeple (the little figure of board games), a moon with a small star, a podium with a star, a pile of boxes and a person, named Games, Nights, Scores, Shelf and Profile. On Scores sits an ivory game piece as thick as a draughts piece, its ridged side just showing underneath, a round groove near the edge and the podium carved in dark green in the middle; the name under it is light and heavier. On arrival the bar rises and the piece drops in from above turning half over, lands on its rim and rattles until it lies flat. Choosing another tab the piece is tossed: it lifts off the felt and rises in an arc over the bar, while its shadow slides sideways and softens, and in the air it turns half over (three half turns for a far tab); edge on it shows its ridged side, and the face that comes up has the new tab's icon carved in it. It arrives a little short of flat, lands on its rim and rattles like a coin spun on a table: the lean goes round faster and faster as it shrinks, the ridged side shows on one side and then the other, the light slides over the face and the carving catches the light on the right side, until a quick shiver and a last little knock as it lies down. The new tab's name lights up as soon as the piece touches, the old one goes out as soon as it leaves, and the printed icon the piece uncovers springs back a little on the felt. With the mouse over another tab, or a finger down on it, the piece leans towards it as if about to jump, and pressing it sinks a touch. Clicking the tab already chosen, the piece hops in place with a full turn. The arrows, Home and End choose from the keyboard.

How it works

A piece made of discs

The piece is a CSS 3D element: eight discs stacked a pixel apart, in two alternating tones, make the ridged side, and on top and underneath there are two faces with backface-visibility hidden, each with an SVG: the round groove and the icon drawn twice, a light lip underneath and the dark cut on top. Edge on the discs would leave gaps: a band at right angles to the faces makes the side solid, and only shows then. The icons on the faces are copied from the bar's own.

The toss

Each toss is a hand-written ballistic flight: along the bar the piece goes straight at a steady speed, its height is a parabola (starting from where it is now, if it is already in the air) and its turn about the upright axis is even. The half turns are odd, so the other face comes up, and that face, while it faces down, is given the new icon; FLIPS adds whole turns for far tabs. The height shows as size and as a rise on the screen, and the shadow on the felt slides sideways, grows and softens.

Landing on the rim

The turn stops TILT degrees short of flat, and at that moment the same rotation is written again as a tilt about an axis in the plane (rotateZ, rotateX, rotateZ back): no jump. From there the tilt shrinks as the square root of the time left, and its direction turns at a speed inversely proportional to the tilt, as in Euler's disk: slow at first, a quick shiver at the end, then a knock. The same tilt, towards a tab, is the gesture when the mouse goes over it.

The light

On every frame, with the same rotations CSS uses, the normal of the face looking up and its two axes on the screen are worked out. The normal gives how much the face brightens or darkens under the light, which comes from the top left, and where the gloss slides; its axes give which side of the carving the light lip is on. The shadow narrows when the piece is edge on and stretches out on the raised side when it leans.

Bar and keyboard

The tabs are buttons in a nav and the chosen one has aria-current="page"; the arrows, Home and End choose and move the focus, which only shows from the keyboard. The loop on GSAP's ticker writes nothing while the piece is still. The library's preview clicks Games, Shelf and Profile 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).
TOSSscript.js0.44The seconds a toss to the next tab takes (a far one takes a little more).
HEIGHTscript.js24How high the piece flies to the next tab, in pixels (a little higher to a far one).
FLIPSscript.js1The extra turns of a far toss (0: every toss a single half turn).
RATTLEscript.js1.15The seconds the piece rattles on its rim before it lies flat (0: it lands flat).
TILTscript.js24How far it leans as it lands on its rim, in degrees.
LEANscript.js12How far it leans towards a tab under the pointer or the finger, in degrees.

Try them in the Playground

When to use it

For the bottom bar of an app made for phones, with four or five sections, when the navigation can have some character: board games, a club, a card or quiz app, an app for children. The piece says where you are even standing still, and the toss shows where you come from and where you go.

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 target of about 69 by 70 pixels. A finger down on a tab makes the piece lean and sink like the mouse over it, and the tap tosses it. Vertically the page still scrolls (touch-action: pan-y).

With reduced motion

The piece does not fly, flip or rattle: it fades off the old tab and back in, flat, on the new one, and the name lights up with a fade. On arrival the bar only fades in, with the piece already in place; no leaning towards the mouse, no press.

Pitfalls

  • Edge on, halfway through the turn, the eight stacked discs looked like a comb with gaps: a band at right angles to the faces makes the side solid, and the faces hide it at every other moment.
  • The turn and the rattle had to be one movement: the turn stops a few degrees short of flat and that same rotation, written again as a tilt about an axis in the plane, becomes the start of the rattle. Written as two movements, the piece jumped on landing.
  • With the first rattle law the tilt shrank at once and the speed-up came when nothing showed any more: now the tilt shrinks as the square root of the time left and the turning speed is inversely proportional to the tilt, so the speeding up shows.
  • Changing tab in mid-air, the new icon goes on the face that will come up, but never changes the one showing at that moment: the face is picked by the parity of the half turns left, and if less than half a turn is left two more are added.
  • The light layer over the carving washed it out when the face turned to the lamp: now it tints the ivory under the carving, which stays sharp. The face turned away went a dirty grey: it darkens less and in a warmer tone.
  • On arrival the loop was already letting the piece fall by gravity, unseen, and when the real drop started it was already down: the toss starts at once with a negative time and the piece waits up high.
  • In flight the shadow slid down onto the tabs' names: the light is high, so the shadow slides mostly sideways and stays in the row of icons.
  • GSAP's transform on the printed icon that springs back would have wiped out the CSS one for the hover: GSAP moves a wrapper, CSS the icon inside. And pressing on the bar and letting go outside left the piece sunk: the whole window listens for the release.

Libraries

GSAP 3.15.0, Gabarito (font variabile) 5.3.0.

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

Testing

Tested on 6 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