Tree

Tree displays hierarchical data with expandable branches.

Basics

Render a hierarchical dataset with collapsing branches. Use indent to control how far each level is inset.

Loading demo…

import { Tree } from '@plocks/ui'; import { TREE_DATA } from './data'; export function Demo() { return <Tree data={TREE_DATA} collapsible indent={20} />; }

Tree with Checkboxes

Enable checkboxes with cascadeCheck to keep parent and child nodes synchronized while tracking checked ids.

Loading demo…

import { useState } from 'react'; import { Tree } from '@plocks/ui'; import { TREE_DATA } from './data'; export function Demo() { const [checkedIds, setCheckedIds] = useState<string[]>(['react', 'css']); return ( <Tree data={TREE_DATA} checkboxes cascadeCheck checkedIds={checkedIds} onCheckedChange={setCheckedIds} expandAll /> ); }

Tree Selection

Set selectionMode to single or multiple and drive selectedIds from state. In multiple mode, shift-click captures a range and Cmd/Ctrl-click toggles one row.

Loading demo…

import { useState } from 'react'; import { Block, Text, Tree } from '@plocks/ui'; import { TREE_DATA } from './data'; export function Demo() { const [selectedIds, setSelectedIds] = useState<string[]>([]); return ( <Block fullWidth> <Tree data={TREE_DATA} selectionMode="multiple" selectedIds={selectedIds} onSelectionChange={setSelectedIds} expandAll /> <Text size="xs" c="secondary"> {selectedIds.length === 0 ? 'Click a row, shift-click for a range, or Cmd/Ctrl-click to toggle.' : `${selectedIds.length} selected`} </Text> </Block> ); }

Tree Filtering

Provide filterQuery with hideFiltered to search the tree. A highlight renderer and noResultsFallback are also available for custom match styling and empty states.

Loading demo…

import { useState } from 'react'; import { Block, Input, Tree } from '@plocks/ui'; import { TREE_DATA } from './data'; export function Demo() { const [filterQuery, setFilterQuery] = useState(''); return ( <Block fullWidth> <Input label="Search technologies" value={filterQuery} onChangeText={setFilterQuery} placeholder="Type to filter the tree" /> <Tree data={TREE_DATA} filterQuery={filterQuery} hideFiltered expandAll={!!filterQuery} /> </Block> ); }

Lazy Loading

Mark a node with hasChildren and supply loadChildren to fetch a branch the first time it opens. The disclosure control shows a loader while the promise is in flight.

Loading demo…

import { Tree, type TreeNode } from '@plocks/ui'; import { TREE_DATA, fetchInstances, fetchVolumes } from './data'; export function Demo() { const loadChildren = (node: TreeNode) => node.id.includes('-web') ? fetchVolumes(node.id) : fetchInstances(node.id); return <Tree data={TREE_DATA} loadChildren={loadChildren} selectionMode="single" showGuides />; }

Custom Tree Rendering

Pass renderLabel to build custom rows with icons and badges while keeping the tree interactions intact.

Loading demo…

import { Badge, Icon, Row, Text, Tree, type TreeNode } from '@plocks/ui'; import { STATUS_BADGES, TREE_DATA, TYPE_ICONS, type CustomNodeData } from './data'; export function Demo() { const renderCustomLabel = (node: TreeNode) => { const data = node.data as CustomNodeData; const status = STATUS_BADGES[data.status]; return ( <Row gap="sm" align="center" style={{ flex: 1 }}> <Icon name={TYPE_ICONS[data.type]} size="sm" /> <Text size="sm" style={{ flex: 1 }}> {node.label} </Text> <Badge variant="outline" c={status.color}> {status.label} </Badge> </Row> ); }; return <Tree data={TREE_DATA} renderLabel={renderCustomLabel} selectionMode="single" expandAll />; }

Virtualized Tree

Pass virtualized with an h to render only the rows in view. Expansion animation is skipped in this mode; filtering, selection and keyboard navigation all still operate on the full row list.

Loading demo…

import { Tree } from '@plocks/ui'; import { TREE_DATA } from './data'; export function Demo() { return <Tree data={TREE_DATA} virtualized h={320} size="sm" striped showGuides />; }
Scan to open this page on your phoneplocks.dev/ui/Tree