Selected: rotterdam
import * as React from "react"
import { Stack } from "@hel/ui/stack"
import { Text } from "@hel/ui/text"When to use it#
Use a tree for data that has levels, where people need to see the shape of it and move through it: a chart of accounts, warehouses and their aisles, an org chart, categories and subcategories. People can open only what they need.
For two or three flat sections that open and close, use an Accordion. For a long flat list, use a Table. If people only pick one value from the tree, a Combobox with groups can be easier to use on a phone.
Import#
import { Tree } from "@hel/ui/tree"
import type { TreeNode } from "@hel/ui/tree"Usage#
The rows come in as nodes, a list of objects. A node with children is a branch that can be opened. Give each node an id that is unique in the whole tree, because the open, selected and checked rows are kept by id.
const nodes: TreeNode[] = [
{
id: "rotterdam",
label: "Rotterdam warehouse",
icon: Warehouse,
meta: "1,240 items",
children: [{ id: "rotterdam-a", label: "Aisle A" }],
},
]
<Tree aria-label="Warehouse locations" nodes={nodes} defaultOpen={["rotterdam"]} />A node takes a label, and optionally a description under it, an icon, initials in a circle instead of an icon, a code before the label, and meta at the end of the row. disabled rows cannot be opened, selected or checked, and the keyboard skips them.
Give the tree an aria-label. It follows the WAI-ARIA tree pattern, with one tab stop. Up and Down move between the rows that are showing, Right opens a branch or moves into it, Left closes it or moves to its parent, Home and End jump to the first and last, and Enter and Space select the row or toggle its checkbox. In a right-to-left layout, Right and Left swap.
Examples#
Selecting a row#
A click selects a row, and opens it if it is a branch. By default the tree keeps its own state, with defaultOpen and defaultSelected. To hold it yourself, pass open and selected with onOpenChange and onSelectedChange, as the preview does for the selected row.
Checkboxes#
checkboxes puts a checkbox on every row, and a click now checks the row, instead of selecting it. Checking a branch checks all the leaves under it, and a branch with only some of them checked shows the in-between state. checked is the list of ids of the leaves only, never of the branches, so that is what you save.
2 permissions granted
import * as React from "react"
import { Stack } from "@hel/ui/stack"
import { Text } from "@hel/ui/text"Loading children when a row opens#
When a branch has a lot of children, fetch them when the row is opened. Give the node loading: true, with no children yet. It can be opened, and shows a loading line under it. When the data arrives, replace the node with loading: false and its children.
import * as React from "react"
import { Stack } from "@hel/ui/stack"
import { Tree } from "@hel/ui/tree"Your own rows#
renderContent replaces what is in each row. Build it from the row parts, TreeToggle, TreeCheckbox, TreeMedia, TreeCode, TreeLabel and TreeMeta, which read the row they are in. The second argument says the level, whether it is a branch, expanded, selected, and its check state. Here the account code comes first, and a branch shows how many children it has. guides={false} turns off the lines down each level, so the levels are only indented, and size is sm, md or lg.
import { Badge } from "@hel/ui/badge"
import { Flex } from "@hel/ui/flex"
import { Tree, TreeCode, TreeLabel, TreeToggle } from "@hel/ui/tree"Props#
Tree#
| Prop | Type | Default |
|---|---|---|
nodes | TreeNode[] | required |
open / defaultOpen | string[] | [] |
onOpenChange | (open: string[]) => void | none |
selected / defaultSelected | string | null | none |
onSelectedChange | (selected: string | null) => void | none |
checkboxes | boolean | false |
checked / defaultChecked | string[] | [] |
onCheckedChange | (checked: string[]) => void | none |
guides | boolean | true |
size | "sm" | "md" | "lg" | "md" |
renderContent | (node, state) => ReactNode | the default row |
TreeNode#
| Field | Type |
|---|---|
id | string, unique in the tree |
label | ReactNode |
description | ReactNode |
icon | LucideIcon |
initials | string |
code | string |
meta | ReactNode |
disabled | boolean |
loading | boolean |
children | TreeNode[] |
The text of the loading line can be changed with messages on the tree, or translated for the whole app in the config.