Tree

A tree of rows for large hierarchies, such as file trees, with keyboard navigation and a context menu.

StableSince 1.0.0
On this page

Import

import { Tree } from '@envsemble/envui';

Usage

<Tree
  aria-label="Files"
  items={[
    {
      value: 'src',
      depth: 0,
      label: 'src',
      icon: <IconFolder />,
      expandable: true,
      expanded: true,
    },
    { value: 'src/index.ts', depth: 1, label: 'index.ts', icon: <IconDescription /> },
  ]}
  value={selected}
  onAction={(row) => setSelected(row.value)}
  onExpandedChange={(row, open) => toggle(row.value, open)}
/>

items are the visible rows in order, flattened: an expanded row is followed by its visible descendants. Each row, a TreeItem, has a stable, unique value, such as a path, which it carries as data-id; its depth, 0 for a top-level row, each level indenting it by 0.75 rem; its label; and an optional decorative icon, a muted description, such as a status or a path, end content, such as a Badge with a count, a native tooltip, textValue for type-ahead where the label isn't plain text, and disabled.

Expanding

expandable gives a row a chevron, and expanded shows its children, the rows that follow it in items. onExpandedChange receives the row and its new state from a press on its chevron, → on a collapsed row and ← on an expanded one, mirrored in right-to-left text; the application updates items, removing a collapsed row's descendants.

Opening, previewing and selection

onAction receives the row people open and { preview }, a TreeActionDetails: true on a single click and Space, false on a double click and Enter. Expandable rows call it too, and disabled rows don't. The tree doesn't select by itself: set value, the selected row's value, from onAction; that row takes the navigation selection colors and scrolls into view when it changes.

Context menu

contextMenu returns the menu items for the row, or for the tree's empty area when the row is null; without it, the browser's own context menu stays. contextMenuLabel names the menu.

Thousands of rows

<div style={{ blockSize: '16rem' }}>
  <Tree
    aria-label="Build logs"
    items={rows}
    onAction={(row) => setSelected(row.value)}
    onExpandedChange={(row, open) => toggle(row.value, open)}
  />
</div>

The tree renders only the rows in view, and is as tall as its parent: give the parent a height, or the tree one through style. Build rows in a useMemo from the expanded set, such as 50 folders of 200 logs each, over 10,000 rows when all are open. className, style and ref reach the tree, the element that scrolls and holds focus.

Density

density is xs, sm, the default, md or lg, the rows' block padding: in the comfortable scale sm rows are 1.75 rem tall and md rows 2.25 rem, and under a coarse pointer every row is at least 2.75 rem.

Accessibility

You provide

  • An aria-label for every tree, and the context menu's name in the application's language.
  • The visible rows, their expansion, the selection, and what opening does.
  • A textValue for rows whose label isn't plain text, and a name for a count in end.

envUI provides

  • A tree of treeitem rows with their level, position, size, expanded, selected and disabled states.
  • One tab stop with aria-activedescendant, the active row kept rendered and in view.
  • Each row's description and end read with its label; icons and chevrons hidden.
  • Rows 2.75 rem tall on coarse pointers.

Props

Tree

A virtualized tree of rows for large hierarchies such as file trees. The application passes the visible rows and owns expansion and selection; the tree owns focus, keyboard navigation and scrolling. It fills its parent's height, scrolls itself and renders only the rows in view.

PropTypeDescription
itemsrequired
readonly Item[]
The visible rows in order, flattened: an expanded row is followed by its visible descendants. The application owns expansion; collapsing a row removes its descendants from items.
aria-labelrequired
string
The tree's accessible name, such as Files.
valueDefault null
stringnull
The selected row's value: it takes the navigation selection colors and scrolls into view when it changes. The application sets it, usually from onAction.
onActionrequired
(item: Item, details: TreeActionDetails) => void
Called when people open a row: with preview true on a single click and Space, and false on a double click and Enter. Expandable rows call it too, and disabled rows don't.
onExpandedChangerequired
(item: Item, expanded: boolean) => void
Called with the row and its new state from a press on its chevron, the right arrow key on a collapsed row and the left arrow key on an expanded one, mirrored in right-to-left text. The application updates items.
contextMenu
(item: Item | null) => ReactNode
Menu items for the row people right-click, or press Shift+F10 or the Menu key on, or for the tree's empty area when the item is null. Without it, the browser's own context menu stays.
contextMenuLabelDefault 'Actions', or EnvUIProvider's messages.tree.contextMenu
string
The context menu's accessible name.
densityDefault 'sm'
'xs''sm''md''lg'
How tightly the rows pack: xs, sm, md or lg changes only their block padding, never the text or the icons. In the comfortable scale sm rows are 1.75 rem tall and md rows 2.25 rem. Under a coarse pointer every row is at least 2.75 rem.

Also accepts the native props of <div>, such as ref, className and aria-* attributes.

Part names

Each part's class and state attributes are public and versioned, for an application's CSS to style.

env-tree
The tree.
env-tree-chevron
The chevron that expands an item.
env-tree-description
An item's description.
env-tree-end
The end of an item, for a count or actions.
env-tree-group
The rows an expanding or collapsing item reveals or hides, while they grow or shrink.
env-tree-icon
An item's icon.
env-tree-item
One item.
env-tree-label
An item's label.
env-tree-rows
The rows of a level.
data-density
lg · md · sm · xs
data-ending-style
Present on env-tree-group
data-expanded
Present on env-tree-chevron
data-focus-visible
Present
data-highlighted
Present on env-tree-item
data-id
Any value, or present on env-tree-item
data-starting-style
Present on env-tree-group, env-tree-item

Strings

EnvUIProvider's messages overrides each, and a component's own label prop wins over it.

messages.tree.contextMenu
"Actions" Names an item's menu of actions.

Changelog

1.0.0

selectedId is value and onOpen is onAction, with TreeActionDetails; a TreeItem's detail is description and its trailing is end. The context menu's default name comes from messages.tree, and the tree takes ref and style. A row's class is env-tree-item (was env-tree-row), and env-tree-detail and -trailing are env-tree-description and -end; a description keeps more room from its label.

1.0.0-prerelease.6

A collapsing row's children fade over the whole collapse, --env-motion-duration-medium, so they leave no empty band.

A row's children grow from its end and fade in as it expands, and shrink and fade out as it collapses, in env-tree-group until they have closed, as an accordion's panel does, while the rows below glide with them; the chevron turns at the pace of the other chevrons.

A row under the pointer takes three quarters of --env-color-bg-row-hover, and pressed the whole of it, a step quieter than a table's rows, so the light tint of the selected row always reads strongest.

1.0.0-prerelease.5

A row is keyed by value, which the tree's value names, where it had id, and its native tooltip is tooltip, where it was title. TreeActionOptions is TreeActionDetails. The tree takes the native props, such as id and data-*.