Skip to content

Tree

An expandable hierarchy such as folders and files or a nested menu, with keyboard navigation and optional selection. Build it from nested TreeItem elements.

Datav0.1.0experimentalWCAG 2.2 AAView spec
src
index.ts
package.json
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 tree

Copies 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.json

Works, 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

Fruit
Apple
Pear
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

Chapter 1
Section 1.1
Section 1.2

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.

PropTypeDefault
labelrequired

Accessible name of the tree, e.g. "Files".

stringnone
childrenrequired

TreeItem elements. Nest TreeItems inside a TreeItem to make children.

nodenone
selectionMode

Whether items can be selected.

"none" | "single" | "multiple""none"
selectedKeys

Controlled selection, by item id.

"all" | Iterable<Key>none
defaultSelectedKeys

Initial selection when uncontrolled.

"all" | Iterable<Key>none
onSelectionChange

Called with the selected item ids.

(keys: "all" | Set<Key>) => voidnone
expandedKeys

Controlled set of expanded items, by id.

Iterable<Key>none
defaultExpandedKeys

Items expanded at first.

Iterable<Key>none
onExpandedChange

Called when items are expanded or collapsed.

(keys: Set<Key>) => voidnone
onAction

Called when an item is activated with Enter or a double-click.

(key: Key) => voidnone

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