Skip to content
animorio
IT
All components

The folding sidebar

Navigation, 3D, Transitions, HoverGSAP, CSS onlyAdvancedCompressed weight: Demo 7.9 KBGSAP 28 KBFreeDownload the zip

The light sidebar of a dashboard with a button to fold it: every name closes in a concertina behind its icon like a strip of paper, one after the other from the top, while the bar narrows to its icons. Folded, pointing at an icon unfolds its name beside it in a little tag.

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 folding sidebar</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">
    <!-- the sidebar of a dashboard: the button at the top folds it down to its icons, and every name folds away behind
         its icon like a strip of paper closing in a concertina, one after the other from the top; folded, the name of an
         icon pointed at unfolds beside it as a little tag -->
    <div class="fs-wrap" data-wrap>
      <nav class="fs" aria-label="Main" data-nav data-autoclic>
        <div class="fs__head">
          <button class="fs__toggle" type="button" aria-expanded="true" aria-controls="fs-list" aria-label="Collapse sidebar" data-toggle>
            <svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="3"/><path d="M9 4V20"/><path class="fs__chev" d="M15.5 9.5L13 12L15.5 14.5"/></svg>
          </button>
        </div>
        <div class="fs__body" data-body>
          <span class="fs__mark" aria-hidden="true" data-mark></span>
          <ul class="fs__list" id="fs-list">
            <li><a class="fs__item" href="#" aria-current="page" data-item>
              <svg class="fs__icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="3.5" width="7" height="7" rx="2"/><rect x="13.5" y="3.5" width="7" height="7" rx="2"/><rect x="3.5" y="13.5" width="7" height="7" rx="2"/><rect x="13.5" y="13.5" width="7" height="7" rx="2"/></svg>
              <span class="fs__label" data-label>Overview</span></a></li>
            <li><a class="fs__item" href="#" data-item>
              <svg class="fs__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 7.5A2 2 0 0 1 5 5.5H9.5L11.5 7.5H19A2 2 0 0 1 21 9.5V17.5A2 2 0 0 1 19 19.5H5A2 2 0 0 1 3 17.5Z"/><path d="M3 10.5H21"/></svg>
              <span class="fs__label" data-label>Projects</span></a></li>
            <li><a class="fs__item" href="#" data-item>
              <svg class="fs__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 6.5A2.5 2.5 0 0 1 6.5 4H17.5A2.5 2.5 0 0 1 20 6.5V14A2.5 2.5 0 0 1 17.5 16.5H10.5L6.5 20V16.5A2.5 2.5 0 0 1 4 14Z"/><path d="M8 9H16M8 12.5H13"/></svg>
              <span class="fs__label" data-label>Messages</span>
              <span class="fs__badge" data-badge>4<span class="fs__sr"> unread</span></span></a></li>
            <li><a class="fs__item" href="#" data-item>
              <svg class="fs__icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="5" width="17" height="15.5" rx="2.5"/><path d="M3.5 9.5H20.5M8 3V6.5M16 3V6.5"/></svg>
              <span class="fs__label" data-label>Calendar</span></a></li>
            <li><a class="fs__item" href="#" data-item>
              <svg class="fs__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 20H20"/><path d="M7 16.5V11M12 16.5V6.5M17 16.5V13.5"/></svg>
              <span class="fs__label" data-label>Reports</span></a></li>
            <li><a class="fs__item" href="#" data-item>
              <svg class="fs__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5.1L13.76 3.17L15.44 3.69L15.83 6.26L16.88 7.12L19.48 7L20.31 8.56L18.77 10.65L18.9 12L20.83 13.76L20.31 15.44L17.74 15.83L16.88 16.88L17 19.48L15.44 20.31L13.35 18.77L12 18.9L10.24 20.83L8.56 20.31L8.17 17.74L7.12 16.88L4.52 17L3.69 15.44L5.23 13.35L5.1 12L3.17 10.24L3.69 8.56L6.26 8.17L7.12 7.12L7 4.52L8.56 3.69L10.65 5.23Z"/><circle cx="12" cy="12" r="2.8"/></svg>
              <span class="fs__label" data-label>Settings</span></a></li>
          </ul>
        </div>
      </nav>
      <!-- the name of an icon pointed at while the bar is folded: a little tag that unfolds beside it -->
      <span class="fs__tip" aria-hidden="true" data-tip></span>
    </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>

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.

24 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 folding sidebar” from Animorio and fit it into my site

What it does

On a white background a light sidebar with rounded corners: at the top a button with the drawing of a sidebar, below it Overview, Projects, Messages with a purple badge reading 4, Calendar, Reports and Settings, each with its line icon. Under Overview there is a lilac ground, and its icon is purple. On arrival the names unfold one after the other from the top while the bar widens. Pressing the button at the top its little arrow turns over and every name folds away: it is cut into three panels hinged one on the next, which turn opposite ways like a strip of paper folded in a concertina, until the name disappears behind its icon; the panels turned away from the light darken, those turned towards it brighten, so the creases show. The rows close one after the other from the top and the bar narrows with them down to the width of the icons, while the badge slides onto the corner of the Messages icon. Pressing again, the names unroll from the top and the bar widens again. Folded, pointing at an icon with the mouse or reaching it with Tab, its name comes out beside it in a dark tag that also unfolds like a concertina; moving to another icon the tag folds shut, moves down and unfolds again with the new name. Choosing a page, the lilac ground slides there, stretching a little in the direction it goes.

How it works

The name cut into panels

The script reads each row's name, measures it and builds PANELS panels a third wide each: every panel shows its slice of the whole word (the same text shifted left by k times the width) and holds the next panel on its right edge, so turning one carries all those after it. The real name stays in the link, hidden from sight, for screen readers.

The concertina

With q from 0 (flat) to 1 (closed) the angle is a = 88° · q: the first panel turns by +a with rotateY on its left hinge, the second by −2a against the first, the third by +2a, so they lie at +a, −a, +a and the strip is w·cos(a) wide. Each panel has its row's ground and a gradient veil that grows with the sine of the angle, dark for those turned away from the light and pale for the others; over the last few degrees the strip fades, so it leaves no edge-on hairlines.

The bar tied to the folds

Each row has its own q, animated by a single gsap.to with a STAGGER-second stagger, from the top both ways. Every frame the bar's width is SHUT + (OPEN − SHUT) times the largest cosine among the rows, so it follows the most open name and never cuts one; the ground under the page chosen and the badge are worked out from the same width, the badge sliding towards the icon's corner with its row's q.

The tag

Folded, the name of the icon pointed at or reached with Tab is cut into the same panels inside a dark tag beside the bar, which unfolds from q = 1 to 0 in TIP_FOLD seconds with back.out; its point only shows once the tag is nearly open. Moving to another icon it folds shut, moves down and unfolds again; Esc closes it, and with a tap it stays out for a second and a half.

The sliding ground and the keys

The ground under the page chosen goes to its row in SLIDE seconds, stretching by 18% from the side it leaves and gathering itself with back.out on arrival. The button has aria-expanded, aria-controls and a label saying what it will do; the links have aria-current. The library's preview takes a tour: it chooses, folds, shows a tag, opens again.

Values to change

NameFileDefaultWhat it changes
PANELSscript.js3How many panels each name folds into.
FOLDscript.js0.62The seconds one name takes to fold or unfold.
STAGGERscript.js0.06The seconds between one name and the next, from the top.
TIP_FOLDscript.js0.42The seconds the tag beside a folded icon takes to unfold.
SLIDEscript.js0.45The seconds the ground under the page chosen takes to slide there.

Try them in the Playground

When to use it

For the sidebar of a dashboard, a back office, a project or messaging app, an admin panel: anywhere the content needs room without losing the navigation.

On phones

The open bar is 236 pixels wide and fits on screen together with the tag; every row is 44 pixels tall. Folded, tapping an icon chooses the page and brings out its name for a second and a half.

With reduced motion

No folds and no sliding: the names and the tag fade in and out, the bar takes its width at once and the ground under the page chosen jumps to its row.

Pitfalls

  • With transparent panels the shadows of the folds looked like grey boxes over the bar: each panel has its row's ground (the bar's, the lilac one of the page chosen or the hover one, taken from a CSS variable), so the gradients read as folded paper.
  • Between one panel and the next a pale one-pixel line was left by rounding: each face runs 0.6 pixels under the next, except the last.
  • The text cut into panels is repeated three times and screen readers would have read it three times: the panels are aria-hidden and the real name stays in the link, hidden only from sight.
  • The button's little arrow, turned with scaleX in CSS, ended up outside the icon: GSAP turns it with transformOrigin, which for SVGs works out the right centre of the path.
  • The names' width must be measured once the typeface has arrived: with document.fonts.ready the panels are cut again at the true width.

Libraries

GSAP 3.15.0, Geist (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 7.9 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