Skip to content
animorio
IT
All components

The file tree on a leash

Trees, Physics, SVG, TransitionsGSAPAdvancedCompressed weight: Demo 16 KBGSAP 28 KBFreeDownload the zip

An architecture studio’s projects in a file tree: a folder opens on a spring and its files slide out from under it, go a little too far and come back, while the branch lines are drawn again every frame. A file picked up and carried stays on the end of its branch, which turns green, follows it and hooks onto the folder under the hand; let go there, the branch reels it into its new place, let go anywhere else it pulls it back home.

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 file tree on a leash</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/host-grotesk@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- an architecture studio's projects in a file tree: folders open on a spring, every branch line is drawn again
         each frame from where the rows really are, and a file carried away stays on the end of its branch, which
         hooks onto the folder under it and reels the file in when it is let go -->
    <section class="explorer" aria-labelledby="explorer-title" data-explorer data-autoclic>
      <header class="explorer__head">
        <h2 class="explorer__title" id="explorer-title">Projects</h2>
        <button class="explorer__tool" type="button" aria-label="Collapse all folders" title="Collapse all folders" data-collapse>
          <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M4.75 2.5L8 5.75l3.25-3.25M4.75 13.5L8 10.25l3.25 3.25"/></svg>
        </button>
      </header>
      <div class="explorer__scroll" data-scroller>
        <div class="canvas" data-canvas>
          <svg class="lines" aria-hidden="true" focusable="false">
            <path class="lines__band" data-band/>
            <path class="lines__hole" data-hole/>
            <path class="lines__guides" data-guides/>
            <path class="lines__wire" data-wire/>
          </svg>
          <div class="tree" role="tree" aria-labelledby="explorer-title" aria-keyshortcuts="Control+X Control+V" data-tree></div>
        </div>
      </div>
      <div class="explorer__foot" data-foot>
        <div class="explorer__foot-in">
          <p class="explorer__note" data-note></p>
          <button class="explorer__undo" type="button" data-undo>Undo</button>
        </div>
      </div>
      <p class="sr" aria-live="polite" data-live></p>
    </section>
  </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.

45 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 file tree on a leash” from Animorio and fit it into my site

What it does

On a white background a light grey panel, Projects, with the button that closes every folder and under it an architecture studio’s archive: three projects (Riverside Pavilion, Harbour Baths, Hill House) with their Drawings and Models folders and their files (plans and sections in pdf, a 3dm model, an xlsx fee proposal, md notes, a dwg survey, a jpg photo), each with an icon drawn for its kind, and manila folders. At first everything is closed, then Riverside Pavilion, its Drawings and Harbour Baths open one after another; ground-floor-plan.pdf is chosen, on a white sheet that starts where its branch ends. Clicking a folder its chevron turns and its files slide out from under it as from a slot, go a little past their place and come back, while the trunk and the curved twigs grow with them; clicking it again they go back in. Picked up with the mouse (or held a moment with a finger), a file’s row comes away: the sheet closes round the name and casts a shadow, a dashed outline stays in its place, and its branch turns green and follows it. The branch hooks onto the folder under the hand, which is tinted with everything inside it: the folder itself when the hand is on its row, the file’s folder when the hand is on a file. Resting a moment on a folder’s row, the file goes to hang just under it on a short branch; resting longer on a closed folder, the folder opens by itself. Let go there, the dashed outline closes, the folder opens if it was closed, a place opens in alphabetical order and the branch reels the file in, arriving with the speed the hand gave it; the folder gives a nod and Moved to … appears at the bottom of the panel with Undo, which takes it back the same way. Let go outside the tree, or with Esc, the branch pulls it back home. With the keys: arrows, Home, End, Enter, and Ctrl+X and Ctrl+V to move a file.

How it works

A slot for every row

Every row is positioned absolutely by one formula: each row has a slot as tall as a row times how open its folders are (a number from 0 to 1, and a little more when the spring overshoots) and times how much it is there itself; the slots are stacked from the top and the row sits at the bottom of its slot, clipped with clip-path: inset. So a folder opening lets its files slide out from under it, folders inside open with it, a file arriving opens its place and one leaving leaves a gap that closes, and no row ever overlaps another.

Springs that can be interrupted

Each folder’s opening is a damped spring (OPEN_SPRING opening, CLOSE_SPRING closing) worked out on GSAP’s ticker in steps of 1/240 of a second, with the frame’s step capped at 1/30; its speed is kept, so a folder clicked again halfway goes back without a jerk. The chevron turns 90 degrees times the opening, so it overshoots too. The rows that cannot be seen get visibility: hidden, so Tab and screen readers do not find them; when nothing moves the loop stops.

Branches drawn again every frame

A single SVG path under the rows, rebuilt from the slots every frame: for every open folder a trunk under its chevron and for every row a twig with a 6 pixel bend (a quadratic), as long as the row is out, stopping a pixel before the row’s sheet: the sheet of the row chosen starts there, and the branch holds it.

The leash

Picked up (after 4 pixels with the mouse, after PRESS seconds still with a finger), a file’s row leaves the formula and follows the hand with translate3d; its slot stays open and dashed. The green branch runs from the root of the folder under the hand (the row’s folder, or the folder itself if it is its row) to the sheet, and the root slides from folder to folder on a spring (HOOK_SPRING). While a file is carried nothing in the tree moves, so the row under the hand is always the one aimed at: resting 0.14 seconds on a folder’s row, a spring draws the file in to hang over the row below with its sheet widened, and the branch fades when it has no room to bend; after HOLD seconds a closed folder opens by itself.

The flight, undo and the keys

Let go over another folder, an empty gap takes the file’s place and closes, a place opens in the folder after the folders in order of name, and the new indent is made up for in the sideways offset in the same frame; then a two-dimensional spring (LAND_SPRING), started with the hand’s speed, takes the row into its place and the green branch fades onto the grey twig. Undo makes the same flight backwards. The tree follows the ARIA tree pattern with a roving tabindex (arrows, Home, End, right opens or goes in, left closes or goes up, Enter chooses or opens) and Ctrl+X, Ctrl+V move a file; a click from a script (the preview) carries a file into another project, undoes it, closes everything and opens the first folders again.

Values to change

NameFileDefaultWhat it changes
OPEN_SPRINGscript.js[260, 0.6]The spring of a folder opening: how stiff it is and how much it is held back (under 1 the files go a little past and come back).
CLOSE_SPRINGscript.js[420, 1]The spring of a folder closing.
LAND_SPRINGscript.js[230, 0.74]The spring taking a file let go to its place, on the end of its branch.
HOOK_SPRINGscript.js[620, 0.9]The spring of the branch’s root sliding from folder to folder under the file carried, and of the file going to hang under a folder.
HOLDscript.js0.6The seconds over a closed folder, carrying a file, before it opens by itself.
PRESSscript.js0.3On a touch screen, the seconds a file is held before it comes away.
OPEN_AT_STARTscript.js['Riverside Pavilion', 'Riverside Pavilion/Drawings', 'Harbour Baths']The folders open at the start, with their path.
TREEscript.js[['Riverside Pavilion', […]], …]The projects: a name and, for a folder, what is inside it.

Try them in the Playground

When to use it

For the file explorer of an editor in the browser, a studio’s archive, a team’s documents, a CMS’s assets: anywhere things move from one folder to another.

On phones

Rows are 44 pixels tall and the panel is at most 336 wide; a tap chooses a file or opens a folder. To pick a file up it is held a moment (PRESS): the row darkens and then comes away, and until then a finger that moves scrolls the page; while the file is in hand the page stays still. The panel never goes over 560 pixels: beyond that the list scrolls inside, a thin line appears under the title and a file landing out of sight is brought into view.

With reduced motion

Folders open and close at once and the rows appearing fade in; the file carried follows the hand, which is what moves it, but let go it goes straight to its place and fades in there, with no flight, the branch’s root does not slide and the folder gives no nod; the line at the bottom of the panel appears without growing.

Pitfalls

  • Opening a space under the folder the file rests on looked like the clearest way, but it moves the rows under the hand: going from a folder to its subfolder the space closed again, the rows rose and the file ended up in the folder below the one aimed at. While a file is carried nothing in the tree moves: the file goes to hang over the row below, with its sheet as wide as the row, covering it whole.
  • With the file level with the folder holding it, the branch became a curl under the chevron: the branch fades when the file is not at least a few pixels below its root, and comes back when the file goes to hang under the folder.
  • The rows hidden from the start, inside closed folders, were never given visibility: hidden because their state never changed: they were invisible but reachable with Tab and by screen readers. The starting state has to be left undefined, not false.
  • The white sheet of the file lifted had its colour transition: for a tenth of a second it was transparent and its name could be read over the row below. Lifted, the sheet turns white at once.
  • A file moving to another folder changes its indent and the row’s content would jump by 16 pixels: the flight’s sideways offset is corrected by the same amount in the same frame.
  • The sheet of the row chosen covered the end of its branch: it starts where the branch ends, a pixel later, and so the branch holds it.
  • Moving in the DOM the row that has the focus, to keep the right order for screen readers, takes the focus away: after the move it is given back.
  • A finger held down and then moved starts the page scrolling, and the browser sends pointercancel: while the file is in hand touchmove is stopped with preventDefault, in a listener that is not passive, and the long-press menu is turned off.
  • The preview crops the panel as it is at rest: a round that opened a closed project made it 160 pixels taller and the bottom went out of the video. Now the round carries the file between two projects already open and the tree keeps its height.

Libraries

GSAP 3.15.0, Host Grotesk (font variabile) 5.3.0.

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