Folder Filing Grid
A document filing React component: scattered document cards fly along an arc into their matching folders, which open to catch them, close with a small dip and roll their counts up. Auto-sort in one go, or file by hand with drag or keyboard, and open a folder to see what is inside.
Filing, turned into something you can watch. Document cards lie scattered on the desk above at deterministic angles and offsets (hashed from each id, so SSR and the client agree). With autoSort on, every interval milliseconds one document flies along an arc into the folder that matches its category — the folder opens as it approaches, closes with a small dip once it lands, and the badge in its corner rolls up by one. You can also drag documents onto folders yourself: the folder under the card opens and highlights, and in strict mode a wrong drop springs back home with a wiggle. Click a folder to open a small panel listing its documents; "Scatter again" sends everything back to the desk. The folders are drawn entirely with CSS gradients — no images.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add folder-filing-grid.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<FolderFilingGrid/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/folder-filing-grid.en.jsonOr, once registries are configured in components.json, install it as @webberui/folder-filing-grid.
Usage
import { FolderFilingGrid } from "@/components/ui/folder-filing-grid";
const documents = [
{ id: "d1", title: "March electricity bill", category: "invoice", meta: "$42.80" },
{ id: "d2", title: "Team retreat sign-up", category: "hr" },
{ id: "d3", title: "Q1 contract", category: "contract" },
];
const folders = [
{ id: "invoice", label: "Invoices" },
{ id: "hr", label: "People", color: "#38bdf8" },
{ id: "contract", label: "Contracts", color: "#a78bfa" },
];
<FolderFilingGrid
documents={documents}
folders={folders}
autoSort
onFiledChange={(filed) => console.log(filed)}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
documents | FilingDocument[] | — | Documents on the desk |
folders | FilingFolder[] | — | The folder row below (3–5 recommended) |
filed | FiledMap | — | Controlled filing result; managed internally when omitted (uncontrolled) |
defaultFiled | FiledMap | {} | Initial filing result in uncontrolled mode |
onFiledChange | (filed: FiledMap) => void | — | Called when the filing result changes (each filed document, or a re-scatter) |
autoSort | boolean | false | Auto-sort: while on, desk documents fly into their folders one by one |
interval | number | 350 | Gap between documents during auto-sort (ms) |
strict | boolean | true | Strict mode: only the matching folder accepts a document and wrong drops bounce back; when off, any folder accepts it |
draggable | boolean | true | Allow dragging documents with mouse / touch |
accent | string | — | Default folder colour (written to --wb-folder-filing-accent); a folder's own color wins |
className | string | — | Passed through to the outermost container |
FilingDocument
| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Unique id; also the key in the filed map |
title | string | — | Document title |
category | string | — | Category; matches a folder id, falling back to the last folder when nothing matches |
meta | string | — | Extra detail (amount, date…) shown under the title |
icon | ReactNode | Document icon | Custom icon |
FilingFolder
| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Unique id, matched against each document's category |
label | string | — | Folder name |
color | string | var(--wb-folder-filing-accent) | Folder colour |
FiledMap
Record<string, string>: document id → folder id. Entries pointing at a folder that does not exist are treated as unfiled.
How it works
- Timing is exposed as CSS variables:
--wb-folder-filing-fly(flight duration, default560ms),--wb-folder-filing-open(folder opening, default180ms) and--wb-folder-filing-accent(default folder colour,#f59e0b). The component re-reads them on every filing, so runtime overrides apply immediately. - Flight path: the component measures the card relative to the folder mouth, eases horizontally with
[0.22, 1, 0.36, 1]and lifts 40px mid-flight for an arc, while rotation returns to zero, scale goes 1 → 0.4 and the card fades out at the end. - The folder's front panel opens with
rotateX(-28deg)around its bottom edge (perspective: 600px), starting--wb-folder-filing-openbefore the document arrives so it catches it; after landing it closes and dips[0, 3, 0]. - Drag hit testing compares the card centre with each folder's
getBoundingClientRect; dropping on empty desk space springs the card back to its slot. - While
autoSortis on the timer keeps running: after "Scatter again" the documents are sorted once more, which suits a showcase loop. To run it once, turnautoSortoff whenonFiledChangereports everything filed (see the demo). - No
Math.random: scatter angles (±8°) and offsets are hashed from id and index, so the first render matches the server output.
Accessibility
- Document cards are
buttons with keyboard instructions viaaria-describedby: Enter selects → arrow keys (or Home / End) move the target folder → Enter files, Esc cancels; the selected card carriesaria-pressed. Clicking a document and then a folder also files it — no dragging required. - After filing with the keyboard, focus moves to the next document still on the desk instead of dropping back to the top of the page.
- Each folder button's
aria-labelcarries its name and count (e.g. "Invoices: 4 filed") and links to its document panel viaaria-expanded/aria-controls; the panel closes on Esc or an outside click, and Esc returns focus to the folder. - Filing, rejection and scatter results are written to an
aria-live="polite"region for screen readers. - When the system's reduce motion setting is on, documents simply disappear, folders neither open nor dip, counts update directly, and scattered documents snap straight back to their slots.
Keystroke Card Deck
A stack of cards answered with number keys 1–3; each answered card flies off and the next slides up — keyboard-first, mouse-friendly.
Roadmap Lane Board
A Now / Next / Later three-lane roadmap board where item cards move between lanes with shared layout animation, supporting both read-only display and interactive dragging.