DEV Community

Cover image for What makes a div read as a folder?
Kirthi Balakrishnan
Kirthi Balakrishnan

Posted on • Originally published at kirthi.studio

What makes a div read as a folder?

The folder gallery on my homepage is about 15 KB of vanilla JavaScript and one SVG path. Almost none of the build time went into the 3D math. It went into a much more annoying problem: the thing kept rendering correctly and still not looking like a folder.

I extracted the component into a small library called flipfolio last week, and packaging it forced me to name what actually carries the illusion. It came down to three things: the silhouette, the front panel, and the depth math. There is also the behind-the-scenes accessibility of it all.

flipfolio: stack, grid, and carousel modes

Where does the folder-ness live?

In the outline, it turns out. The whole silhouette - tab, shoulders, body - is a single SVG path in a 480 by 342 viewBox, stretched to fit with preserveAspectRatio set to none. Swap the path and you swap the identity: a right-side tab, a tabless tray, a sharper cut, all the same engine with one attribute changed.

But a path alone is not a folder. It is a colored blob with a tab :)

What separates a folder from a colored rectangle?

A gap. A real folder is two pieces of material with space between them, so the component is built that way: the SVG path is the back, and a separate front panel covers the lower three quarters. Contents sit between the two planes and slide out of the mouth on hover.

The front panel pays for itself twice. The active card's front is translucent with a backdrop blur, so whatever is inside shows through as a frosted hint, and the four cards behind it stay fully opaque. That split is partly a design call and partly survival: backdrop-filter does not stack politely, and five blurred layers running at once will spin up a laptop fan.

Color works the same way - one input, three surfaces. You pass a single hex per folder and the engine derives the rest: the back sits 30 points darker, the frosted front 40 lighter at 55 percent alpha, the solid front 35 lighter. You configure a color; you get a skin.

How much math does depth actually need?

Waaaay less than I thought. Each card behind the active one gets a transform computed from its distance: position 1 sits 28 pixels higher and 25 back, position 2 sits 60 higher and 62 back. Both terms carry a squared component, so the pile compresses toward the rear the way paper actually stacks. Add a 3 degree tilt per step and a shrinking scale, and 8 folders read as something you could pick up. I can't claim the squared falloff is perceptually correct - it is just the curve that visually felt good enough.

Getting photo mode setup was the most time-consuming part of this. I wrapped an image around the entire folder, tab included, then added a highlight along the fold... and it looked like terrible. Photos now print on the front panel only, the back and tab keep their color.

What about the parts you cannot see?

To a screen reader the gallery is a listbox: real options, roving tabindex, arrow keys, and position announcements through a live region. Wheel and touch navigation both work. Under prefers-reduced-motion the tilt drops and transitions collapse to a frame.

Tools and limits

The core is dependency-free vanilla JavaScript, about 15 KB before compression, with 40 tests across it and its wrappers. React and Vue get thin adapters. There is a web component if you would rather skip frameworks entirely. All 3 layouts share one normalized transform function.

Everything renders on the client, so there is no SSR of the layout itself. And every folder is a live 3D layer, which puts the sweet spot at a dozen items, not hundreds. I built it for things you curate. It's a port_folio_.

Release notes

0.3.0 (July 12, 2026). Grid view renders each folder at its true size now, so the labels stay crisp instead of shrinking away. Runtime theming: setColor re-derives a folder's palette and setGradient paints or clears a front gradient, live. Adds getters, drag events (fg-flingstart / fg-flingend), silhouette presets, more CSS tokens, and a robustness pass, including a fix for undefined wrapper props quietly overriding the loop and scroll defaults. 72 tests, up from 46.

0.2.0 (July 12, 2026). Stack mode gets a drag gesture: grab the front folder and throw it. The pile answers the moment you let go, and the thrown folder sails off, then drops back in from above. A short drag springs back, a real throw advances. drag: 'off' turns it off. 6 new tests, and the landing was verified frame by frame on WebKit, where 3D transition bugs like to hide.

0.1.0 (July 11, 2026). First release: three modes, peek, photo decals, React and Vue wrappers, a web component, zero dependencies.

The package is on npm as flipfolio.

Demo: https://kirthi.studio/demos/flipfolio/
Code: https://github.com/kirthi-b/flipfolio

Top comments (0)