Tree
A tree shows nested items, such as files and folders, in rows that open and close to any depth.
<tk-tree label="Files"></tk-tree>
<tk-tree label="Files"></tk-tree>
<script type="module">
const tree = document.querySelector('tk-tree');
tree.data = [
{ id: 'src', label: 'src', children: [
{ id: 'components', label: 'components', children: [{ id: 'button', label: 'button.ts' }] },
{ id: 'index', label: 'index.ts' },
] },
{ id: 'readme', label: 'README.md' },
];
tree.expanded = ['src'];
</script>Anatomy
| Part | What it is |
|---|---|
tk-tree |
the whole tree; one Tab stop |
| node | one row with a label; nodes come from the data property |
| chevron | on a node with children; turns when the node opens |
| group | a node's children, indented one step |
A node is { id, label, children? }. A node without children is an end node. The tree
never changes your data; it keeps track of what is open and selected itself.
Selection
selection |
What it does |
|---|---|
none (default) |
nothing is selectable; for navigating |
single |
one node at a time; selecting another replaces it |
multiple |
any number of nodes |
tree.selection = 'multiple';
tree.selected = ['button', 'index'];
tree.addEventListener('tk-selection-change', (e) => console.log(e.detail.selected));Selecting is not opening. Space or a click selects; Enter or a click also fires
tk-activate, which means "open this one". A file browser usually uses both.
The arrow keys never change the selection, so you can select scattered nodes with the keyboard: arrow to one, press Space, arrow to the next, press Space again.
Sizes
There is one size. For a denser list, set --tk-tree-row-height on the tree; change the
indent with --tk-tree-indent.
When to use
- Use a tree for nested data people explore, such as a file browser, a category picker, or an org chart.
- Use a
Data table withexpandablewhen the rows have columns. - Use an
Accordion for one level of sections with headings, and aCollapsible for a single show and hide. - Use a
Sidebar when the rows are links to other pages. - Use a
Select ,Multiselect , orRadio group for a flat list of choices. - Keep the nesting meaningful. A tree two items deep is a list with extra key presses.
| Tree | Sidebar | Accordion | |
|---|---|---|---|
| Rows are | items to select or explore | links to pages | sections with headings |
| Depth | any | rows with nested rows | one |
| Tab stops | one for the whole tree | one per row | one per section |
Content
- Use names, not sentences: "Invoices", "Q3 revenue.xlsx".
- Keep labels short. A long label is cut off with an ellipsis.
- Don't number levels in the label, like "1.2.3 Budget". The tree announces the level itself.
Do / Don't
| ✅ Do | ❌ Don't |
|---|---|
| Use a tree for nested data | Use it for a flat list of choices |
| Use a data table when rows have columns | Put columns in tree labels |
| Use a sidebar for links to pages | Make a site's navigation a tree |
| Name nodes like files: "Invoices" | Write "Click here to see the invoices" |
Give the tree a label |
Leave the tree unnamed |
Related
Data table : nested rows with columns.Sidebar : nested links to pages.Accordion andCollapsible : flat sections that open and close.
API
Attributes
| Attribute | Type | Default | What it does |
|---|---|---|---|
label |
string | — | the tree's accessible name; required |
selection |
none | single | multiple |
none |
what can be selected |
Properties
| Property | Type | What it does |
|---|---|---|
data |
TreeNode[] |
the nodes: { id: string; label: string; children?: TreeNode[] } |
expanded |
string[] |
ids of the open nodes to start with |
selected |
string[] |
ids of the selected nodes to start with |
expandedItems |
string[], read-only |
ids of the open nodes now |
selectedItems |
string[], read-only |
ids of the selected nodes now |
Methods
expand(id)andcollapse(id): open or close one node.setExpanded(ids)andsetSelected(ids): replace the open or selected nodes.focusItem(id): move focus to a node.
Events
| Event | Detail | Cancelable | When |
|---|---|---|---|
tk-toggle |
{ id, open } |
yes | before a node opens or closes |
tk-selection-change |
{ selected } |
yes | before the selection changes |
tk-activate |
{ id } |
no | someone pressed Enter on a node or clicked it |
Cancel tk-toggle or tk-selection-change to keep the state in your own store:
tree.addEventListener('tk-activate', (e) => openFile(e.detail.id));
tree.addEventListener('tk-toggle', (e) => {
e.preventDefault();
store.toggle(e.detail.id);
tree.setExpanded(store.openIds);
});CSS
--tk-tree-row-height: the minimum row height;space-32by default.--tk-tree-indent: one level of indent;space-16by default.
Tokens
| Part | Tokens |
|---|---|
| node | text/default, Body Small (14/20), radius-8 |
| hover / press | state/hover / state/press |
| selected node | state/on; state/on-hover under the pointer |
| chevron | icon/secondary, icon-size-16 |
| focus ring | border/accent/focus, inside the row |
Values per theme:
Accessibility
Built in:
- A tree with nested groups; each node reports its level and position, such as "level 2, 3 of 7".
- One Tab stop; the arrow keys move inside, so people don't Tab through every file.
- With
selection="multiple", the tree is marked multi-selectable and each node reports whether it's selected. - Typing a letter jumps to the next node that starts with it.
- With reduced motion, the chevron turns without animation.
Your part:
- Give the tree a
label. - Handle
tk-activate: the tree doesn't open or navigate anything itself.
| Key | Action |
|---|---|
Down / Up |
moves to the next or previous visible node; never changes the selection |
Right |
opens a closed node; on an open node, moves to its first child |
Left |
closes an open node; otherwise moves to the parent |
Home / End |
moves to the first or last visible node |
Enter |
fires tk-activate |
| a letter or digit | jumps to the next node that starts with what you type |
Space |
selects or deselects the focused node |
Shift + Down / Up |
moves and selects the next or previous node (multiple) |
Cmd / Ctrl + A |
selects every node (multiple) |
Contrast limits
- Selected node: in light and dark, its tint matches a hovered node's. Screen readers always hear the state. If it must be clear at a glance, show it elsewhere too.