An expandable hierarchy such as folders and files or a nested menu, with keyboard navigation and optional selection. Build it from nested TreeItem elements.
import { Tree, TreeItem } from "@rdloom/react";
export default function TreeBasicExample() {
return (
<div className="w-72">
<Tree label="Files" defaultExpandedKeys={["src"]}>
<TreeItem id="src" title="src">
<TreeItem id="components" title="components">
<TreeItem id="button" title="button.tsx" />
<TreeItem id="menu" title="menu.tsx" />
</TreeItem>
<TreeItem id="index" title="index.ts" />
</TreeItem>
<TreeItem id="docs" title="docs">
<TreeItem id="readme" title="README.md" />
</TreeItem>
<TreeItem id="package" title="package.json" />
</Tree>
</div>
);
}Installation
npx rdloom add treeCopies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.
Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/tree.jsonWorks, but without upgrade tracking.
Usage
import { Tree, TreeItem } from "@rdloom/react";
<div className="w-72">
<Tree label="Files" defaultExpandedKeys={["src"]}>
<TreeItem id="src" title="src">
<TreeItem id="components" title="components">
<TreeItem id="button" title="button.tsx" />
<TreeItem id="menu" title="menu.tsx" />
</TreeItem>
<TreeItem id="index" title="index.ts" />
</TreeItem>
<TreeItem id="docs" title="docs">
<TreeItem id="readme" title="README.md" />
</TreeItem>
<TreeItem id="package" title="package.json" />
</Tree>
</div>Selectable
import { Tree, TreeItem } from "@rdloom/react";
export default function TreeSelectableExample() {
return (
<div className="w-72">
<Tree label="Categories" selectionMode="multiple" defaultExpandedKeys={["fruit"]} defaultSelectedKeys={["apple"]}>
<TreeItem id="fruit" title="Fruit">
<TreeItem id="apple" title="Apple" />
<TreeItem id="pear" title="Pear" />
</TreeItem>
<TreeItem id="veg" title="Vegetables">
<TreeItem id="carrot" title="Carrot" />
</TreeItem>
</Tree>
</div>
);
}Controlled
Expanded: a
import { useState } from "react";
import { Tree, TreeItem } from "@rdloom/react";
export default function TreeControlledExample() {
const [expanded, setExpanded] = useState<Set<string>>(new Set(["a"]));
return (
<div className="flex w-72 flex-col gap-2 text-sm">
<Tree label="Outline" expandedKeys={expanded} onExpandedChange={(keys) => setExpanded(new Set([...keys].map(String)))}>
<TreeItem id="a" title="Chapter 1">
<TreeItem id="a1" title="Section 1.1" />
<TreeItem id="a2" title="Section 1.2" />
</TreeItem>
<TreeItem id="b" title="Chapter 2">
<TreeItem id="b1" title="Section 2.1" />
</TreeItem>
</Tree>
<p aria-live="polite">Expanded: {[...expanded].join(", ") || "none"}</p>
</div>
);
}API Reference
Defined by the spec. Components also accept the props of the React Aria component they wrap.
| Prop | Type | Default |
|---|---|---|
labelrequiredAccessible name of the tree, e.g. "Files". | string | none |
childrenrequiredTreeItem elements. Nest TreeItems inside a TreeItem to make children. | node | none |
selectionModeWhether items can be selected. | "none" | "single" | "multiple" | "none" |
selectedKeysControlled selection, by item id. | "all" | Iterable<Key> | none |
defaultSelectedKeysInitial selection when uncontrolled. | "all" | Iterable<Key> | none |
onSelectionChangeCalled with the selected item ids. | (keys: "all" | Set<Key>) => void | none |
expandedKeysControlled set of expanded items, by id. | Iterable<Key> | none |
defaultExpandedKeysItems expanded at first. | Iterable<Key> | none |
onExpandedChangeCalled when items are expanded or collapsed. | (keys: Set<Key>) => void | none |
onActionCalled when an item is activated with Enter or a double-click. | (key: Key) => void | none |
Accessibility
Role tree, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.
Keyboard
- Tab moves into the tree, on one item, and out again
- Up and Down arrows move between visible items
- Right arrow expands a collapsed item, or moves to its first child; Left arrow collapses, or moves to its parent
- Home and End jump to the first and last visible item
- Enter activates the item (onAction); Space selects it when selection is on
- Typing a letter jumps to the next item starting with it
Screen readers announce
- Entering announces the tree name, the item, its level and position, and whether it is expanded
- Expanding or collapsing announces the new state
- Moving to a child announces its level
What your code must do
- A named tree whose items expose aria-level, aria-expanded (when they have children) and aria-selected (when selectable)
- Only one item is in the tab order at a time (roving tabindex)
- The chevron is decorative for assistive technology: expanding is done from the keyboard and with the item itself
- Selection is shown by a filled row and aria-selected, not only color
Guidelines
Use it when
- Folders and files, categories, outlines and settings groups
- Navigation with more than one level
Avoid it when
- Flat lists: use ListBox or a menu
- Tabular data with columns: use Table or DataGrid
- Hierarchical rows with several columns of data: DataGrid tree data
Don't
- Trees deeper than four levels
- Loading every branch up front when there are thousands
- Using a tree for page navigation without links inside
Design tokens
The semantic tokens this component uses. Change them once and every component follows; see Design tokens.
--rd-color-surface-default--rd-color-surface-subtle--rd-color-surface-selected--rd-color-border-default--rd-color-text-default--rd-color-text-muted--rd-color-focus-ring--rd-radius-control