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

ts
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

Keys
When
Effect
ArrowDown / ArrowUp
tree
Moves the active row through the visible rows without changing the selected file.
ArrowRight
tree, active directory
Opens a closed directory; otherwise moves to the next visible row.
ArrowLeft
tree, active directory
Closes an open directory; otherwise moves to the previous visible row.
Enter / Space
tree
Toggles a directory or selects the active file.
Keys
ArrowDown / ArrowUp
When
tree
Effect
Moves the active row through the visible rows without changing the selected file.
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