Resolved: light

Tree

The STD tree kit (src/components/ds/tree/) — the workflow-primitive family for hierarchical navigation and file/folder browsing. Both are thin, presentational compositions over the Surface base primitive and the @phauna/ds WAI-ARIA Tree. Everything takes plain props + callbacks — no server actions, no Prisma rows.

composition
TreeView wraps @phauna/ds/grid's <Tree> (re-exported, not forked) and adds default accessors + an optional recessed Surface frame. FileBrowser = a framed TreeView pane + a Surface detail/list pane + a DS <Breadcrumbs> path trail. No tree behaviour is re-implemented.
a11y
role=tree → role=treeitem rows carrying aria-level / aria-expanded / aria-selected; roving tabindex (exactly one focusable node). Arrow Up/Down move, Arrow Right/Left open/close or hop parent↔child, Home/End jump, Enter/Space select. Selection reads via aria-selected + an accent rail (never colour alone); transitions respect reduced-motion.
presentational / controlled
Selection lives in selectedId and flows out via onSelect; expansion is the DS Tree's own uncontrolled state (seed with defaultExpanded). FileBrowser is controlled-or-uncontrolled (selectedId vs defaultSelectedId). No fetch, no router, no server actions.
tokens
All visuals come from the @phauna/ds .tree* catalog + the Surface token variants (--color-surface*, --color-accent*, --color-rule, --radius-*, --dur-*, --ease-brand). Flip the theme to confirm a full re-skin.

TreeView

A thin wrapper over the @phauna/ds WAI-ARIA Tree. Pass a `nodes` array (id / label / children / optional icon + meta) and it just works — default accessors read node.id / node.children / node.label, override any to project a custom shape. `framed` nests the tree in a recessed Surface well with its own scroll viewport; selection is controlled. Click a row or use the keyboard (arrows / Home / End / Enter).

framed (recessed Surface well)

Teams12
March planning372
Outreach88
Regional5
Northeast41
Southwest19
Officials1.2k

selectedId: teams-march

unframed (bare role=tree)

Events7
Spring rally
Phonebank night
Officials1.2k
framed + unframed · icon + meta slots · controlled selection (both panes share selectedId)
nodes / getId / getChildren / renderLabel
nodes: TreeNode[] (id, label, children?, icon?, meta?). The three accessors default to node.id / node.children / node.label; override to drive a custom node shape without reshaping your data.
selectedId / onSelect
Controlled single selection — selectedId highlights a row (aria-selected + accent rail); onSelect(node) fires on click + Enter/Space. Expansion is uncontrolled.
defaultExpanded
Ids expanded on first mount (the DS Tree manages expansion thereafter). Branch ids only; leaves are ignored.
framed / maxHeight
framed nests the tree in a recessed Surface (inset) well; maxHeight (px number or CSS length, default 20rem) bounds that well's scroll viewport.

FileBrowser

A two-pane file/folder browser: a framed TreeView on the left, a Surface detail/list pane on the right. Selecting a folder shows a DS Breadcrumbs path trail plus a clickable list of its immediate children (drill down without touching the tree); selecting a file shows its metadata + description. Folder/file icons are injected when a node doesn't supply one.

campaign-2026
docs
brief.md
talking-points.md
drafts
README.md

docs

Type
Folder
Items
2
Modified
Apr 3, 2026

Contents

last picked:

tree pane + detail/list pane · folder → child list, file → metadata · controlled-or-uncontrolled
nodes
FileBrowserNode[] = TreeNode + kind ('folder' | 'file', inferred from children when omitted), size, modified, description. The right pane renders from whichever node is selected.
selectedId / defaultSelectedId / onSelect
Controlled (selectedId) or uncontrolled (defaultSelectedId seeds internal state). onSelect(node) fires for both tree rows and detail-list rows with the resolved node.
detail pane
folder → DS <Breadcrumbs> trail + a clickable list of immediate children (each picks that child); file → trail + a Type/Size/Modified definition list + description. Empty selection shows emptyHint.
treeLabel / detailActions / treeMaxHeight
treeLabel names the tree landmark; detailActions slots nodes into the detail header's top-right; treeMaxHeight bounds the tree pane's scroll viewport (default 24rem).