Skip to content

Tree / file tree

In review
Live · web

File tree: arrow-key nav · click a file to select (one cyan indicator)

src
components
demos
TreeDemo.tsx
ButtonDemo.tsx
Example.astro
package.json
logo.png

Task subtree: multi-select · checking a parent propagates (tri-state)

· Native Android app
· File tree screen
· Tasks sheet
· Epic / child rows
· Subtask badge
· SSE streaming

Selected file: TreeDemo.tsx · checked subtasks: 2. The island is live.

// Web: React, consuming @dizyx/nockerl-tokens. NockerlTree ships from
// @dizyx/nockerl-react: the recursive rows, depth indent + guide-line rails,
// disclosure chevrons, single-select as an accent border + cyan ink (law §6:
// state by outline, no wash) OR multi-select tri-state checkboxes (parent↔child
// propagation), and the full WAI-ARIA tree keyboard pattern are all in the package.
import { useState } from 'react';
import { NockerlTree, type NockerlTreeNode } from '@dizyx/nockerl-react';
const files: NockerlTreeNode[] = [
{
id: 'src', name: 'src', kind: 'folder', children: [
{ id: 'src/components', name: 'components', kind: 'folder', children: [
{ id: 'src/components/Tree.tsx', name: 'Tree.tsx', kind: 'file', fileType: 'typescript' },
] },
{ id: 'src/styles', name: 'styles', kind: 'folder', count: 3, children: [/* … */] },
],
},
{ id: 'package.json', name: 'package.json', kind: 'file', fileType: 'json' },
];
export function FileTree() {
const [expanded, setExpanded] = useState(new Set(['src', 'src/components']));
const [selected, setSelected] = useState<string | null>(null);
return (
<NockerlTree
nodes={files}
selectable="single" // or "multi" for tri-state checkboxes
ariaLabel="Project files"
expandedIds={expanded}
onToggleExpand={setExpanded}
selectedId={selected}
onSelect={setSelected}
/>
);
}

NockerlTree takes the recursive nodes forest + controlled expand/select state. Auto-expand root folders on first load to match the canonical behaviour. A NockerlTreeNode is { id, name, kind: 'folder' | 'file', fileType?, children?, count?, loading?, disabled? }.

PropTypeDefaultDescription
nodes *NockerlTreeNode[]The recursive forest. Auto-expand root folders on first load to match the canonical behaviour.
selectableNockerlTreeSelectable'single'single: one selected node (a cyan ring + cyan ink, no fill). multi: tri-state checkboxes that propagate parent↔child.
ariaLabel *stringREQUIRED accessible name for the role="tree" container.
expandedIds *Set<string>Controlled set of expanded folder ids.
onToggleExpand(next: Set<string>) => voidFired with the next expanded set when a chevron (or a folder row) toggles.
selectedIdstring | nullSelected node id (single-select).
onSelect(id: string) => voidFired when a leaf is activated (single-select).
checkedIdsSet<string>Checked leaf ids (multi-select). A folder reads on / off / mixed from its descendants.
onCheckedChange(next: Set<string>) => voidFired when a node toggles its whole leaf-subtree (multi-select).
Cross-platform drift (tracked for reconciliation)
  • The recursive tree ships on all three platforms. Web publishes NockerlTree (@dizyx/nockerl-react) with both single-select and multi-select tri-state checkboxes. Swift publishes NockerlTree (NockerlDesign), riding the native List(children:) over NockerlTreeNode. Compose ships its own real hierarchy (FileTreeRow + FileTreeViewModel flattening FileTreeNode → FlatFileNode, plus the EpicListRow/ChildListRow task subtree). The native Swift + Android trees are single-select today. Multi-select tri-state checkboxes (plus git-status badges and async-loading folders) are on web now and a follow-up for Swift/Android. Voice’s older flat VocabRow / HistoryRow (expandedID) rows are the accordion idiom, not this hierarchy.
  • Tree vs list vs accordion. A list is flat sectioned rows; an accordion is a few stacked collapsible sections; a tree is a recursive, multi-level hierarchy with per-node expand/collapse, depth indentation + guide lines, and the full WAI-ARIA tree keyboard pattern. Reach for tree only when nesting is genuinely recursive (a file tree, a task subtree).
  • Indent step: Compose uses 20dp per level for files (depth * 20) and 28dp for the two-tier task subtree; the shipped web NockerlTree mirrors the 20px file-tree step. One indent token should win across platforms.