- Keys
ArrowDown / ArrowUp- When
- tree
- Effect
- Moves the active row through the visible rows without changing the selected file.
Docs Platform
FileTree
FileTree presents a repository or folder hierarchy. The machine owns which directories are open, which row is active, and which file is selected. Headless parts add the tree semantics; styled components add Grassroot's visual treatment; vanilla mounts the same behaviour without a framework.
Explore the layers
Machine is pure state. Headless maps the state to one shared DOM and ARIA contract. Styled keeps that contract and adds the visual recipe. Vanilla is the framework-free mount.
Install
npm i @grassroot/ui-machines
import { createService } from "@grassroot/statechart"import { fileTreeMachine } from "@grassroot/ui-machines/file-tree" const nodes = [ { name: "src", children: [{ name: "index.ts" }, { name: "App.tsx" }] }, { name: "README.md" },]const service = createService(fileTreeMachine, { input: { nodes, defaultSelected: "/src/index.ts" }, runCommand: (command) => console.log(command),}) service.start()service.send({ type: "OPEN", id: "/src" })service.send({ type: "MOVE", direction: 1 })service.send({ type: "ACTIVATE", id: "/src/App.tsx", reason: "keyboard" }) service.getSnapshot().context.selected // "/src/App.tsx"service.stop()Behaviour contract
Every layer follows the same rules.
- Rows: nodes flatten in source order. A directory's path is built from its slash-separated names, and its children appear only while it is open.
- Selection: selected is controlled, while defaultSelected starts an uncontrolled selection. A file activation reports the proposed path and reason; a controlled owner adopts it on its next update.
- Active row: the tree keeps real focus. Keyboard movement is exposed through aria-activedescendant and the active row's data attributes; individual rows do not take focus.
- Directory state: defaultOpen sets each directory's starting state. Clicking or activating a directory toggles it without selecting a file.
Anatomy
The shared headless parts are:
FileTree.Root data-part=root context owner and stable scope
FileTree.Header data-part=header repository name and selected path
FileTree.Tree data-part=tree focusable role=tree keyboard owner
FileTree.Row data-part=row visible role=treeitem and depth
Rows expose aria-expanded for directories and aria-selected for the selected file. The tree exposes its active row with aria-activedescendant. Closed descendants are not rendered.
Keyboard
ArrowDown / ArrowUpArrowRightArrowLeftEnter / Space- Keys
ArrowRight- When
- tree, active directory
- Effect
- Opens a closed directory; otherwise moves to the next visible row.
- Keys
ArrowLeft- When
- tree, active directory
- Effect
- Closes an open directory; otherwise moves to the previous visible row.
- Keys
Enter / Space- When
- tree
- Effect
- Toggles a directory or selects the active file.
Accessibility
Give the tree an accessible name through its repository label or an accessible label on the tree. The tree owns keyboard focus and points to the active row with aria-activedescendant. Directory rows expose their open state, and selected files expose their selected state. The browser contract runs axe scans while the tree is closed and open.
Evidence & further reading
-
file-tree.contract.ts, the shared browser contract, run against the vanilla mount and all five framework adapters, including both accessibility scans. -
file-tree.spec.md, the reviewable machine, anatomy, keyboard, selection, and SSR contract. - Accordion: the disclosure walkthrough. The closest sibling for open state and keyboard movement.
- DropdownMenu: the active-descendant walkthrough. The closest sibling for keyboard-owned active rows.