Tree
A hierarchical list, such as a file browser, following the ARIA tree view pattern. It is one stop in the tab sequence. The arrow keys move through the nodes on screen, and open and close them.
import { Tree, TreeGroup, TreeItem, TreeItemLabel } from 'terracotta/tree';Anatomy
<Tree> {/* role="tree", owns selection and expansion */}
<TreeItem> {/* role="treeitem" */}
<TreeItemLabel/>{/* names the item */}
<TreeGroup> {/* role="group"; makes the item a parent */}
<TreeItem/>
</TreeGroup>
</TreeItem>
</Tree>Examples
Basic
Parents and end nodes
An item with a TreeGroup is a parent node. It gets aria-expanded, and its
group is shown while it is expanded. An item without one is an end node, with
no aria-expanded, so it is not announced as a parent.
TreeGroup takes unmount, as Transition does. By
default a closed group is removed.
Labels
Put the item's name in a TreeItemLabel. The item points aria-labelledby at
it, so the names of its children are not read as part of its own name.
Type-ahead matches this text.
Selection
value and defaultValue work as on Listbox. Every item
needs a value.
- A single-select tree holds one value. Clicking a node, or pressing Enter or Space on it, selects it.
multiplemakes the tree hold an array, and setsaria-multiselectable="true". Enter and Space toggle the focused node.- Selection does not follow focus. The arrow keys only move focus.
- A
disableditem stays focusable but cannot be selected.
Pass by when the values are objects.
Expansion
defaultExpanded lists the values of the items that start open. Pass
expanded and onExpandedChange to control it.
const [open, setOpen] = createSignal<string[]>(['docs']);
<Tree aria-label="Files" defaultValue={undefined} expanded={open()} onExpandedChange={setOpen}>
…
</Tree>Drawing the rows
TreeItem takes a render prop with the item's state. Use it for the
indentation and an open or closed marker:
<TreeItem value={node.id}>
{item => (
<>
<span style={{ 'padding-inline-start': `${item.level() - 1}rem` }}>
{item.isParent() ? (item.isExpanded() ? '▾' : '▸') : ''}
<TreeItemLabel>{node.name}</TreeItemLabel>
</span>
{node.children && <TreeGroup>…</TreeGroup>}
</>
)}
</TreeItem>Style the row inside the item, not the item itself. The item contains its group, so a background on the item would cover its children too.
Naming the tree
Give the tree an aria-label, or point aria-labelledby at a visible heading.
State attributes
| Element | Attribute | Present when |
|---|---|---|
Tree |
tc-tree |
Always |
TreeItem |
tc-tree-item, tc-owner |
Always |
TreeItem |
tc-level |
Always, set to the item's depth from 1 |
TreeItem |
tc-selected |
The item is selected |
TreeItem, TreeGroup |
tc-expanded |
The item is expanded |
TreeItem |
tc-disabled |
The item is disabled |
TreeItemLabel |
tc-tree-item-label |
Always |
TreeGroup |
tc-tree-group |
Always |
Reading the item state
| Member | Type | Description |
|---|---|---|
level() |
number |
The depth of the item, from 1 for a root node. |
isSelected() |
boolean |
Whether the item is selected. |
isExpanded() |
boolean |
Whether the item is open. |
isParent() |
boolean |
Whether the item has a TreeGroup. |
disabled() |
boolean |
Whether the item is disabled. |
toggle() |
() => void |
Opens or closes the item. |
select() |
() => void |
Selects the item, or toggles it in a multi-select tree. |
Keyboard
| Key | Action |
|---|---|
| ↓ / ↑ | Next / previous node on screen |
| → | Opens a closed node. On an open node, moves to its first child. |
| ← | Closes an open node. Otherwise, moves to its parent. |
| Home / End | First / last node on screen |
| Enter / Space | Selects the node, or toggles it in a multi-select tree |
| Printable characters | Type-ahead: moves to the next node whose label starts with what you typed |
| Tab | Moves into the tree, on the last focused node, else the first selected node, else the first node |
The pattern's optional keys are not included: * to open every sibling, and Shift and Ctrl combinations for selecting ranges.
API
<Tree>
Renders a <ul> by default. Pass exactly one of defaultValue or value.
| Prop | Type | Default | Description |
|---|---|---|---|
as |
ValidConstructor |
'ul' |
Element or component to render as. |
multiple |
boolean |
false |
Holds an array of selected values. |
defaultValue |
V | V[] | undefined |
none | Initial selection, uncontrolled. |
value |
V | V[] | undefined |
none | Current selection, controlled. |
onChange |
(value) => void |
none | Called with the new selection. |
toggleable |
boolean |
false, true with multiple |
Whether selecting a selected node deselects it. |
by |
(a: V, b: V) => boolean |
reference equality | Compares values. |
disabled |
boolean |
false |
Blocks selection. |
defaultExpanded |
V[] |
[] |
Values of the items that start open. |
expanded |
V[] |
none | Values of the open items, controlled. |
onExpandedChange |
(expanded: V[]) => void |
none | Called with the new open items. |
ref |
DynamicNode<T> | (el) => void |
none | Handle to the rendered element. |
| …rest | props of as |
none | Forwarded to the rendered element. |
<TreeItem>
Renders an <li> by default.
| Prop | Type | Default | Description |
|---|---|---|---|
value |
V |
none | Identifies the item. Required. |
disabled |
boolean |
false |
Keeps the item from being selected. |
children |
JSX.Element | (item: TreeItemProperties) => JSX.Element |
none | Contents, or a render prop. |
Rendered attributes: role="treeitem", tabindex (0 on one item, -1 on
the rest), aria-level, aria-selected, aria-expanded on parent nodes,
aria-labelledby while a TreeItemLabel is mounted, aria-disabled.
<TreeItemLabel>
Renders a <span> by default.
<TreeGroup>
Renders a <ul> by default, with role="group". It takes unmount.
Every part takes as and forwards the rest of its props.