Skip to content
animorio
IT
All components

The curling sidebar

Navigation, SVG, Text, Physics, TransitionsGSAPAdvancedCompressed weight: Demo 14 KBGSAP 28 KBFreeDownload the zip

The sidebar of a plant nursery's app, where nothing slides and nothing fades: everything rolls up and unrolls like fern fronds. The names wind letter by letter into a tighter and tighter spiral, the highlight unrolls out of the chosen page's icon with its tip still curled, the line of the greenhouses unrolls downwards and each greenhouse unrolls its name as the line reaches it; folding the bar, every name rolls up into its icon.

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 curling sidebar</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/commissioner@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the sidebar of a plant nursery's desktop app. Nothing in it slides or fades: the names, the highlight under
         the page chosen and the line of the greenhouses roll up and unroll like the fronds of a fern. The script
         draws the moving parts (the letters, the highlight, the line) on the two layers of the body; the names in the
         rows stay for screen readers -->
    <div class="fr-wrap" data-wrap>
      <nav class="fr" aria-label="Nursery" data-nav data-autoclic>
        <div class="fr__head">
          <button class="fr__toggle" type="button" aria-expanded="true" aria-controls="fr-list" aria-label="Collapse sidebar" data-toggle>
            <svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="4.5" width="18" height="15" rx="3.2"/><path d="M9 4.5V19.5"/><path class="fr__toggle-chev" d="M15.6 9.6L13.2 12L15.6 14.4"/></svg>
          </button>
        </div>
        <div class="fr__body" data-body>
          <!-- under the rows: the highlights and the line of the greenhouses -->
          <svg class="fr__ground" aria-hidden="true" data-ground>
            <defs>
              <linearGradient id="fr-frond" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="236" y2="0">
                <stop class="fr__stop-base" offset="0"/>
                <stop class="fr__stop-tip" offset="1"/>
              </linearGradient>
              <clipPath id="fr-clip-ground"><rect data-clip/></clipPath>
            </defs>
            <g data-ribbons></g>
            <g clip-path="url(#fr-clip-ground)" data-ribbons-sub></g>
            <path class="fr__guide" d="M0 0" data-guide/>
          </svg>

          <ul class="fr__list" id="fr-list">
            <li><a class="fr__item" href="#" data-item>
              <svg class="fr__icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3.9"/><path d="M12 2.8V4.9M12 19.1V21.2M2.8 12H4.9M19.1 12H21.2M5.5 5.5L7 7M17 17L18.5 18.5M5.5 18.5L7 17M17 7L18.5 5.5"/></svg>
              <span class="fr__label" data-label>Today</span></a></li>
            <li><a class="fr__item" href="#" aria-current="page" data-item>
              <svg class="fr__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6.8 14.2H17.2L16 20.2C15.9 20.7 15.5 21 15 21H9C8.5 21 8.1 20.7 8 20.2Z"/><path d="M12 14.2V9.6"/><path d="M12 11.2C12 7.9 9.9 5.9 6.4 5.9C6.4 9.2 8.5 11.2 12 11.2Z"/><path d="M12 9.6C12 6.6 13.9 4.5 17.4 4.5C17.4 7.6 15.4 9.6 12 9.6Z"/></svg>
              <span class="fr__label" data-label>Plants</span></a></li>
            <li class="fr__group">
              <button class="fr__item fr__item--parent" type="button" aria-expanded="true" aria-controls="fr-houses" data-item data-parent>
                <svg class="fr__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M3.6 20.4V10.8L12 4.2L20.4 10.8V20.4Z"/><path d="M12 4.2V20.4M3.6 14.8H20.4M7.8 7.5V20.4M16.2 7.5V20.4"/></svg>
                <span class="fr__label" data-label>Greenhouses</span>
              </button>
              <ul class="fr__sub" id="fr-houses" data-sub>
                <li><a class="fr__item fr__item--child" href="#" data-item><span class="fr__label" data-label>North house</span></a></li>
                <li><a class="fr__item fr__item--child" href="#" data-item><span class="fr__label" data-label>Propagation</span></a></li>
                <li><a class="fr__item fr__item--child" href="#" data-item><span class="fr__label" data-label>Shade tunnel</span></a></li>
              </ul>
            </li>
            <li><a class="fr__item" href="#" data-item>
              <svg class="fr__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4.4 10.2H12.8V18.4C12.8 19.6 11.8 20.6 10.6 20.6H6.6C5.4 20.6 4.4 19.6 4.4 18.4Z"/><path d="M5.8 10.2C5.8 5.4 11.4 5.4 11.4 10.2"/><path d="M12.8 15.6L19 10"/><path d="M17.8 8.6L20.4 11.4"/></svg>
              <span class="fr__label" data-label>Watering</span>
              <span class="fr__count" data-count>3<span> plants to water</span></span></a></li>
            <li><a class="fr__item" href="#" data-item>
              <svg class="fr__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3.6L20 7.6V16.4L12 20.4L4 16.4V7.6Z"/><path d="M4 7.6L12 11.6L20 7.6M12 11.6V20.4M8 5.6L16 9.6"/></svg>
              <span class="fr__label" data-label>Orders</span></a></li>
            <li><a class="fr__item" href="#" data-item>
              <svg class="fr__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6.6 3.6H17.4C18.4 3.6 19.2 4.4 19.2 5.4V18.6C19.2 19.6 18.4 20.4 17.4 20.4H6.6C5.6 20.4 4.8 19.6 4.8 18.6V5.4C4.8 4.4 5.6 3.6 6.6 3.6Z"/><path d="M8.6 3.6V20.4"/><path d="M12.4 8.2H16M12.4 11.4H15"/></svg>
              <span class="fr__label" data-label>Journal</span></a></li>
          </ul>

          <ul class="fr__list fr__list--foot">
            <li><a class="fr__item" href="#" data-item>
              <svg class="fr__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7.5H8.2M13 7.5H20M4 16.5H11M15.8 16.5H20"/><path d="M10.6 5.2C11.9 5.2 13 6.2 13 7.5S11.9 9.8 10.6 9.8 8.2 8.8 8.2 7.5 9.3 5.2 10.6 5.2Z"/><path d="M13.4 14.2C14.7 14.2 15.8 15.2 15.8 16.5S14.7 18.8 13.4 18.8 11 17.8 11 16.5 12.1 14.2 13.4 14.2Z"/></svg>
              <span class="fr__label" data-label>Settings</span></a></li>
          </ul>

          <!-- over the rows: the letters, the count and the arrow of the greenhouses -->
          <svg class="fr__ink" aria-hidden="true" data-ink>
            <defs><clipPath id="fr-clip-ink"><rect data-clip/></clipPath></defs>
            <g data-words></g>
            <g clip-path="url(#fr-clip-ink)" data-words-sub></g>
          </svg>
        </div>
      </nav>
      <!-- the name of an icon, beside the folded bar -->
      <svg class="fr__tags" aria-hidden="true" data-tags></svg>
    </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.

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

What it does

On a white background, alone in the middle, the light sidebar of a plant nursery's app, 520 pixels tall and 256 wide, the colour of misted glass, with a grey green edge and a soft shadow. At the top the button that folds it, then Today, Plants, Greenhouses with its three houses (North house, Propagation, Shade tunnel) hanging from a thin line, Watering with a blue 3 (the plants to water), Orders and Journal; at the bottom Settings. Each row has its own drawn icon: a sun, a little plant in a pot, a greenhouse, a watering can, a crate, a notebook, sliders. Under the page chosen there is a green pill that gets paler towards the right, like the new growth of a leaf. On arrival the names unroll one after the other from the top: each one starts wound in a little spiral beside its icon, straightens letter by letter and at the end its tip flicks a little past straight before it stops; the line of the greenhouses comes down unrolling like a fern shoot, and each house unrolls its name when the line reaches it. Choosing another page the green pill rolls backwards into its icon, its tip thinning into a stem that winds up, while a new pill unrolls out of the chosen page's icon, its still curled tip opening last; the icon gives a little jump. With the mouse over a row a grey pill unrolls to just past the name and holds there with its tip curled, like a fern not quite open; choosing that row, the grey pill unrolls all the way and turns green. Greenhouses opens and closes its houses: closing, the houses' names roll up from the bottom and the line climbs back winding into its icon, while the rows below move up; if the page chosen was a house, the pill moves onto Greenhouses. The button at the top folds the bar: every name rolls up into its icon, one row after the other, the 3 and Greenhouses' arrow go into the spiral first, the bar narrows with the spirals down to the icons alone and the green pill stays as a bud round the chosen page's icon; the 3 hops onto the corner of the watering can. Folded, pointing at an icon (or reaching it with Tab) unrolls its name beside the bar in a dark tag; opened again, the names unroll from the top once more.

How it works

A strip with a spiral at its tip

Every moving part (a name, the pill, the line of the greenhouses, the tag) is a strip of length L with a front f. Along the strip u is a length of name: up to the front it is straight and full size; past the front it turns by 1/COIL radians for every pixel of name while the size falls as 2^(−(u − f)/TIGHT), and the step along the curve is the size times du. So the radius falls with the letters and the tip is a logarithmic spiral, a fern's fiddlehead, with the letters packed tight all the way in. The points are added up from the start in steps of 2 pixels, with the turn and the size taken in the middle of each step, only on the frames the strip moves.

The front on a spring

The front moves with c, from 0 (straight) to 1 (all in the spiral): f = L·(1 − c). Unrolling, c follows a spring damped so that it overshoots zero by FLICK (the damping comes from −ln FLICK / √(π² + ln² FLICK)), and while c is below zero the last stretch bends the other way: the tip flicks. Rolling up the spring is critically damped and stops at the spiral; in the last hundredths the spiral shrinks into the front and is gone. The spring takes four steps a frame, with the frame capped at 1/30 of a second, and stops once the front is within a twentieth of a pixel.

The letters on the curve

Each name is split into SVG letters measured with a canvas in the same typeface (the width of each prefix, so the kerning stays), and each letter stands on the strip where its middle falls: moved to the point, turned with the curve and as big as the spiral is there. The 3 and Greenhouses' arrow are the last letters of their strip, which runs to the end of the row: they go into the spiral first and fade early, before they turn into dots. The real names stay in the rows, hidden from view, for screen readers.

The pill and the bud

The highlight is a wide strip drawn as a band: full up to the front, then it rounds off like the end of a pill down to a thin stem that rolls up and shrinks with the spiral; the stem only starts to turn at the end of the round end. One is needed for the page chosen and one for the row under the mouse, which stops PEEK pixels past the name; the ones let go roll up, shrink from the root and are thrown away. Folded, the front stops at 4 pixels: what is left is the round end, a bud round the icon.

The greenhouses and the bar that narrows

The line of the greenhouses is a strip that comes down from the icon and rolls up to the right; the section is as tall as the line has unrolled, and a house's name unrolls when the line's front passes its height. Hidden houses are inert. Folding, the names roll up from the top with STAGGER seconds between them (the ones with the 3 or the arrow a little earlier, as they have the empty stretch to cross) and every frame the bar is as wide as the rightmost point still unrolled; opening again, the width comes back on a spring and the names unroll behind it.

Keys, tags and the preview

The pages are links with aria-current, Greenhouses is a button with aria-expanded and aria-controls, the button at the top has aria-expanded and says Collapse or Expand sidebar; everything works with Tab and has the green focus ring. Folded, the mouse or Tab on an icon unrolls its name in a dark tag beside the bar (after a tap it stays 1.3 seconds), Esc rolls it up. A click from a script on the bar (the library's preview) plays the next step of a tour: Watering, Propagation, close the greenhouses, fold, Journal with its name, open, open the greenhouses, Plants.

Values to change

NameFileDefaultWhat it changes
UNFURLscript.js0.6The seconds a name takes to unroll, up to the flick of its tip.
CURLscript.js0.42The seconds a name takes to roll back up.
STAGGERscript.js0.04The seconds between one row and the next as the bar folds or opens.
COILscript.js9The radius of the outer turn of a roll, in pixels.
TIGHTscript.js30The pixels of name over which a roll gets half as big: less closes it sooner.
FLICKscript.js0.18How far the tip flicks past straight as it unrolls (0: it just stops).
PEEKscript.js10How far the grey pill of a row under the mouse unrolls, in pixels past the name (0: none).

Try them in the Playground

When to use it

For the sidebar of a desktop app with character: a nursery or plant app, a journal, a creative tool, a home or garden app, the dashboard of a shop that wants a signature of its own.

On phones

The bar is 256 pixels wide open and 64 folded, 520 tall: at 390 pixels it fits the screen whole. With no hover there is no grey pill; folded, a tap on an icon chooses the page and unrolls its name for 1.3 seconds.

With reduced motion

Nothing rolls: the names, the line of the greenhouses and the tags appear and disappear with a fade, the pill moves from row to row by fading, the greenhouses' section opens at once and the bar narrows only once the names are gone, with the pill turning into the bud; on arrival everything is already in place.

Pitfalls

  • With the curvature growing along the curve but the letters at their usual spacing, in the spiral the letters turned into separate dots. The right spiral is made in the length of the name: the turn per pixel of name is constant and the step along the curve falls with the size, so the letters stay packed and the radius falls with them.
  • A band 36 pixels wide cannot wind in the plane into a spiral smaller than itself: the tip of the pill came out as a cut corner. Now the end stays round and only a thin stem, starting where the round end finishes, rolls up.
  • Folded, the pill is a bud with its front at 4 pixels: shrinking the whole spiral into the front took the round end away too and left half a disc. The spiral only shrinks past the round end.
  • The 3 inside the spiral turned into a blue dot: the count and the arrow fade as soon as they get small, before they look like dots.
  • The lists are positioned, so a row's offsetTop is relative to its list: Settings ended up over Today. The rows are measured going up the offsetParents to the bar's body.
  • The greenhouses' letters and pills are not inside the section (which cuts with overflow hidden) but in the two SVG layers: to keep them out of sight under the closed section, a clipPath cuts them to the section's rectangle, updated every frame.
  • Redrawing every letter every frame meant six thousand setAttribute calls a second even with nothing moving: now only the strip that moved or the row that moved is redone, and at rest the bar touches nothing.

Libraries

GSAP 3.15.0, Commissioner (font variabile) 5.3.0.

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