Skip to content
Cynosurev0.5.0
SearchStorybookGitHubnpmColour mode: System. Switch to Light

Components / Structure

Tree

A hierarchical list people can open, close and select from: folders and files, a JSON or DOM tree, nested settings. It follows the WAI-ARIA tree view pattern, loads children on demand and can render hundreds of thousands of rows.

Pointer selects and chevron toggles

When to useLink to "When to use"

  • Nested data where the hierarchy matters: a file system, a document outline, an inspector.

Use something else when:

  • there is only one level: a list or a Menu
  • sections of content open and close: Accordion

UsageLink to "Usage"

tsx
Copy
import { Tree } from "@arshad-shah/cynosure";

<Tree label="Files" items={files} defaultExpanded={["src"]} onSelectionChange={([id]) => openFile(id)} />

Selection and expansion work controlled (selected, expanded) or uncontrolled (defaultSelected, defaultExpanded).

Children on demandLink to "Children on demand"

Give an item hasChildren and no children, and pass loadChildren. The first time it opens, a loading row shows, then the children. If loading fails, a row says so and retries with Enter or a click.

Lazy children load fail and retry

Very large treesLink to "Very large trees"

With virtual, only the rows in view are in the page, so a document with 100,000 visible rows stays fast. Give the tree a height; all rows must share one height. The focused row always stays mounted, so the keyboard keeps working after scrolling.

Virtual hundred thousand rows

Custom rowsLink to "Custom rows"

renderLabel replaces the row text, for a value next to a key, a badge or a count. Type-ahead still matches the plain label.

Custom row content

A schema explorerLink to "A schema explorer"

Groups (kind: "group") head the rows under them: Enter, Space and a click open and close them, they are never selected, and they end with their child count. Items take an icon colour (iconTone, iconColor), a trailing count or badge, your own data (handed back in every callback), actions (buttons at the row's end) and a contextMenu. A pressed action or a chosen command calls onCommand(value, item).

tsx
Copy
<Tree<Table>
  label="Schema"
  items={[{ id: "tables", label: "Tables", kind: "group", children: tables.map((t) => ({
    id: t.name, label: t.name, icon: table, iconTone: "accent", trailing: format(t.rows), data: t,
    actions: [{ value: "view", label: "View data", icon: rows }],
    contextMenu: [{ value: "view", label: "View data" }, { value: "copy", label: "Copy name" }],
  })) }]}
  onCommand={(command, item) => run(command, item.data)}
/>
Row actions from the keyboard

Actions show while their row is hovered, focused or selected, and always on screens without hover. On the focused row they come right after it in the Tab order: Tab reaches them, Shift+Tab or Escape goes back to the row, and the arrows still move between rows. The tree keys stay those of the APG pattern and the tree keeps a single way in; every action can also be in the context menu, which Shift+F10 opens.

Context menu on rows

FilteringLink to "Filtering"

filter shows only matching items, with their ancestors opened and their own children, and marks the matched text with HighlightedText. A string matches anywhere in the label, ignoring case. A function returns true, false, or the positions a fuzzy matcher found, which are marked exactly. The expanded ids are not changed, and an opened ancestor can still be closed. Only known children are searched.

Filter shows matches with their ancestors

Loading from a storeLink to "Loading from a store"

When children are fetched somewhere else (a store), put them in children when they arrive and say what is happening with loadingIds and errorIds. onRetry is called when a failed item is retried; renderError(item, retry) and renderEmpty(item) draw the error row and the row under an open item with no children. With loadChildren, the handle's reload(id) loads an item's children again.

Loading and errors from astore

PropsLink to "Props"

Every prop of Tree
PropWhat it doesTypeDefault
labelRequired
Accessible name of the tree.string
itemsRequired
The top-level items; each can hold more under children.TreeItem<T>[]
selected
Selected ids (controlled) and the default for uncontrolled use.string[]
defaultSelected
Ids selected at the start when uncontrolled.string[]
onSelectionChange
Called with the selected ids when the selection changes.((ids: string[]) => void)
expanded
Expanded ids (controlled) and the default for uncontrolled use.string[]
defaultExpanded
Ids expanded at the start when uncontrolled.string[]
onExpandedChange
Called with the expanded ids when an item is expanded or collapsed.((ids: string[]) => void)
selectionMode
single (default): one at a time. multiple: Space and clicks toggle."single" | "multiple"
loadChildren
Fetch the children of an item that hasChildren but has none yet.((item: TreeItem<T>) => Promise<TreeItem<T>[]>)
loadingIds
Items whose children are loading somewhere else (a store): a loading row shows under them while open and they have no children yet; with children, the item is only marked busy.readonly string[]
errorIds
Items whose children failed to load somewhere else: an error row shows under them in place of any children.readonly string[]
onRetry
Called when a failed item is retried (Enter or a click on its error row, or renderError's retry).((item: TreeItem<T>) => void)
renderError
Content of the error row of an item whose children failed. retry runs loadChildren again and onRetry.((item: TreeItem<T>, retry: () => void) => ReactNode)
renderEmpty
Content of a row shown under an open item that has no children ("No columns"). Without it, nothing shows.((item: TreeItem<T>) => ReactNode)
filter
Show only matching items, with their ancestors (opened) and their own children, and mark the matched text. A string matches the label anywhere, ignoring case. Only known children are searched. Pass a stable function (useCallback): branches closed while filtering open again when it changes.TreeFilter<T>
renderLabel
Custom row content after the chevron and icon (a value, a badge, a line number). label is the label as the tree would draw it (with matches marked while filtering). The item's label still drives type-ahead.((item: TreeItem<T>, label: ReactNode) => ReactNode)
onAction
Enter (or a double-click) on an item: its default action, such as opening it or copying its path. Space selects.((item: TreeItem<T>) => void)
onCommand
Called with the value of a pressed row action or a chosen context menu command, and the row's item.((value: string, item: TreeItem<T>) => void)
handle
Imperative control: reload(id).Ref<TreeHandle>
virtual
Render only the rows in view (for tens of thousands of rows). Give the tree a height (style or a class); rows must share one height.boolean
size
Row height and indent. Default "md"."sm" | "md""md"
labels
Replace the built-in English text: the loading row, the failed-to-load row and the context menu's name.Partial<TreeLabels>
className
Added to the root element, for layout.string
style
Layout only. Restyle through --cy-tree-* tokens.CSSProperties

Generated from the type definitions in @arshad-shah/cynosure 0.5.0.

Behaviour and accessibilityLink to "Behaviour and accessibility"

Behaviour and accessibility, table 1
InputResult
Down arrow / Up arrowNext / previous visible row (disabled and loading rows are skipped)
Right arrowOpens a closed branch; on an open one, moves to its first child
Left arrowCloses an open branch; otherwise moves to the parent
Home / EndFirst / last visible row
EnterSelects the row (only that row), or runs onAction; on a group, opens or closes; on a failure row, retries
SpaceSelects; with selectionMode="multiple", toggles; on a group, opens or closes
TabFrom the focused row, to its action buttons; then out of the tree
EscapeFrom an action button, back to its row
Shift+F10, context menu keyOpens the row's context menu
*Opens every branch at the same level
LettersJump to the next row whose label starts with them
Click / chevronSelects / opens or closes without selecting
  • Roles: role="tree" (with aria-multiselectable in multiple mode) and treeitem rows with aria-level, aria-posinset, aria-setsize, aria-expanded, aria-selected (not on groups), aria-busy while children load and aria-keyshortcuts="Shift+F10" with a context menu. A row with actions is named by its label and trailing text.
  • Context menu: one ContextMenu beside the tree, not inside it (a tree holds only tree items); focus returns to the row.
  • Focus: one Tab stop; the focused row is the only row in the Tab order, followed by its action buttons.
  • Direction: in right-to-left text, Left and Right swap, and closed chevrons point left.
  • Performance: only expanded branches are rendered, and rows are memoised, so moving focus re-renders two rows even in a tree of thousands.
Keyboard follows the apg tree

TouchLink to "Touch"

Rows are at least 44px tall on coarse pointers. Row actions always show on screens without hover, with 44px hit areas, and a long press opens a row's context menu.

ThemingLink to "Theming"

Tokens: --cy-tree-indent, --cy-tree-row-height, --cy-tree-selected-bg, --cy-tree-group-ink (group rows) and --cy-tree-trailing-ink (trailing text; on a selected row it takes the row's colour).

ts
Copy
createTheme({ components: { tree: { indent: "20px", rowHeight: "28px" } } });

HighlightedText · ContextMenu · Accordion · Menu · Combobox

These examples run Cynosure 0.5.0 (commit 50ce30b), the version on npm.

Getting started
Theming
Icons
Accessibility
Code editors and highlighters
Using Cynosure without React
Build-tool adapters
Components
Accordion
AppShell
AspectRatio
Breadcrumbs
Calendar
Card
Container
Disclosure
Divider
DocumentViewport
Grid
Inline
PageHeader
PanZoomCanvas
ScrollArea
SideNav
SortableList
SplitPane
Stack
StatusBar
Tabs
TitleBar
Toolbar
Tree
VirtualList
ZoomControls
Announcer
Avatar
Badge
CodeBlock
DescriptionList
EmptyState
ErrorBoundary
Field
Heading
IconTile
Image
Kbd
List
LoadingDots
Notice
ProgressBar
Prose
Skeleton
Stat
Steps
Table
Text
Button
Checkbox
Checklist
ColorField
ColorSwatches
Combobox
CompareSlider
CopyButton
DateField
FileDrop
IconButton
KeyValueEditor
Link
NumberField
Pagination
PathField
RadioGroup
SearchField
SegmentedControl
Select
SelectionFrame
SignaturePad
Slider
Switch
TagInput
TextArea
TextField
ThemeToggle
TimeField
ButtonBase
ChoiceBase
DialogBase
FileInputBase
HiddenValue
Icon
InputBase
LinkBase
LiveRegion
TextAreaBase
ChatMessage
Choice
DataGrid
DeviceFrame
HighlightedText
HotkeyField
ListRow
OptionList
OverlayFrame
QRCode
RowActions
Section
SiteFooter
SiteHeader
SkipLink
SortButton
VisuallyHidden
CommandPalette
ContextMenu
Dialog
FloatingToolbar
HoverCard
Menu
Menubar
Popover
ProgressOverlay
Sheet
Toast
Tooltip
Changelog