Tree
Accessible, unstyled, data-driven tree view with typed nodes, lazy loading, and virtualization.
| Package | @vizejs/ui/tree |
| Maturity | stable |
| Own the source | vize lib pull tree |
| Requires | collection, context, controllable-state, id, sortable, typeahead, virtualizer |
| Aliases | tree view, treeview, file tree, hierarchy, outline, nested list |
| Covers | WAI-ARIA tree view pattern, React Aria Tree, Ark UI Tree View, Reka UI Tree, MUI Tree View |
Usage
import { Tree, TreeRoot, TreeItem, TreeItemCheckbox, TreeItemToggle } from "@vizejs/ui/tree";
Or copy the source into your project with vize lib pull tree (see Source Distribution).
API
Tree<T, K extends TreeKey>
Source: src/families/data/tree/tree-root.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string | null |
undefined |
Consumer-owned tree base id. null and undefined select a deterministic fallback. |
items (required) |
readonly T[] |
required |
Root nodes in display order. |
getKey (required) |
(node: T) => K |
required |
Resolve a node's stable key. Keys must be unique across the whole tree. |
getChildren |
(node: T) => readonly T[] | null | undefined |
undefined |
Resolve a node's children. null and undefined mean "no children yet", which lets loadChildren fetch them lazily; an empty array marks a leaf. |
hasChildren |
(node: T) => boolean |
() => true |
Whether a node without resolved children may lazily load some. Only consulted with loadChildren. |
loadChildren |
(node: T, context: TreeLoadContext) => Promise<readonly T[]> |
undefined |
Asynchronously load a node's children the first time it expands. |
getTextValue |
(node: T) => string |
undefined |
Text used by typeahead. undefined reads the rendered row text, so virtualized trees should provide it to reach rows outside the window. |
isDisabled |
(node: T) => boolean |
undefined |
Disable individual nodes. Disabled rows stay focusable but cannot expand, select, or check. |
expanded |
readonly K[] |
undefined |
Controlled expanded keys (v-model:expanded). undefined selects uncontrolled behavior. |
defaultExpanded |
readonly K[] |
[] |
Initially expanded keys for uncontrolled use. |
selected |
readonly K[] |
undefined |
Controlled selected keys (v-model:selected). Single mode keeps at most one key. |
defaultSelected |
readonly K[] |
[] |
Initially selected keys for uncontrolled use. |
checked |
readonly K[] |
undefined |
Controlled checked keys (v-model:checked), normalized by checkPropagation. |
defaultChecked |
readonly K[] |
[] |
Initially checked keys for uncontrolled use. A cascading parent key checks its subtree. |
selectionMode |
TreeSelectionMode |
"single" |
Selection model: none, one key, or a set of keys. |
selectionFollowsFocus |
boolean |
false |
Whether arrow-key focus also selects in single selection mode. |
expandOnClick |
boolean |
false |
Whether clicking an expandable row also toggles it. |
checkable |
boolean |
false |
Expose aria-checked on rows and make Space toggle checkboxes instead of selection. |
checkPropagation |
TreeCheckPropagation |
"cascade" |
How checking a node affects its descendants and ancestors. |
disabled |
boolean |
false |
Disable the whole tree and remove it from sequential focus order. |
dir |
TreeDirection |
undefined |
Reading direction used to map ArrowRight and ArrowLeft to expand and collapse. undefined inherits DirectionProvider/LocaleProvider, then "ltr". |
typeahead |
boolean |
true |
Move focus to the next row whose text starts with typed characters. |
typeaheadTimeout |
number |
500 |
Idle time in milliseconds before typeahead starts a new query. |
virtualizer |
TreeVirtualizer |
undefined |
Windowing adapter from useTreeVirtualizer. The tree element becomes the scroll viewport. |
reorder |
TreeReorderController<K> |
undefined |
Drag and Alt+Arrow reorder adapter from useTreeReorder. |
ariaLabel |
string |
undefined |
Accessible name when no visible label or aria-labelledby supplies one. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the tree. |
ariaDescribedby |
string |
undefined |
Space-separated ids that describe the tree. |
Events
| Event | Payload | Description |
|---|---|---|
update:expanded |
[keys: readonly K[]] |
Fired with the next expanded keys whenever expansion changes. |
update:selected |
[keys: readonly K[]] |
Fired with the next selected keys whenever selection changes. |
update:checked |
[keys: readonly K[]] |
Fired with the next normalized checked keys whenever a checkbox changes. |
action |
[key: K, node: T, nativeEvent: Event] |
Fired when Enter or a double click activates a row, for example to open a file. |
load |
[key: K, children: readonly T[]] |
Fired after loadChildren resolves for a node. |
loadError |
[key: K, error: unknown] |
Fired when loadChildren rejects for a node. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
TreeSlotState<T, K> |
TreeItem rows. Receives rendered rows, every visible row, and selection state. |
Exposed
| Member | Type | Description |
|---|---|---|
activeKey |
— | |
checked |
— | |
collapse |
— | |
collapseAll |
— | |
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
expand |
— | |
expandAll |
— | |
expanded |
— | |
focus |
— | |
focusKey |
— | |
getCheckedState |
— | |
id |
— | |
reload |
— | |
selected |
— | |
setSelected |
— | |
toggle |
— | |
toggleChecked |
— |
TreeRoot<T, K extends TreeKey>
Source: src/families/data/tree/tree-root.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string | null |
undefined |
Consumer-owned tree base id. null and undefined select a deterministic fallback. |
items (required) |
readonly T[] |
required |
Root nodes in display order. |
getKey (required) |
(node: T) => K |
required |
Resolve a node's stable key. Keys must be unique across the whole tree. |
getChildren |
(node: T) => readonly T[] | null | undefined |
undefined |
Resolve a node's children. null and undefined mean "no children yet", which lets loadChildren fetch them lazily; an empty array marks a leaf. |
hasChildren |
(node: T) => boolean |
() => true |
Whether a node without resolved children may lazily load some. Only consulted with loadChildren. |
loadChildren |
(node: T, context: TreeLoadContext) => Promise<readonly T[]> |
undefined |
Asynchronously load a node's children the first time it expands. |
getTextValue |
(node: T) => string |
undefined |
Text used by typeahead. undefined reads the rendered row text, so virtualized trees should provide it to reach rows outside the window. |
isDisabled |
(node: T) => boolean |
undefined |
Disable individual nodes. Disabled rows stay focusable but cannot expand, select, or check. |
expanded |
readonly K[] |
undefined |
Controlled expanded keys (v-model:expanded). undefined selects uncontrolled behavior. |
defaultExpanded |
readonly K[] |
[] |
Initially expanded keys for uncontrolled use. |
selected |
readonly K[] |
undefined |
Controlled selected keys (v-model:selected). Single mode keeps at most one key. |
defaultSelected |
readonly K[] |
[] |
Initially selected keys for uncontrolled use. |
checked |
readonly K[] |
undefined |
Controlled checked keys (v-model:checked), normalized by checkPropagation. |
defaultChecked |
readonly K[] |
[] |
Initially checked keys for uncontrolled use. A cascading parent key checks its subtree. |
selectionMode |
TreeSelectionMode |
"single" |
Selection model: none, one key, or a set of keys. |
selectionFollowsFocus |
boolean |
false |
Whether arrow-key focus also selects in single selection mode. |
expandOnClick |
boolean |
false |
Whether clicking an expandable row also toggles it. |
checkable |
boolean |
false |
Expose aria-checked on rows and make Space toggle checkboxes instead of selection. |
checkPropagation |
TreeCheckPropagation |
"cascade" |
How checking a node affects its descendants and ancestors. |
disabled |
boolean |
false |
Disable the whole tree and remove it from sequential focus order. |
dir |
TreeDirection |
undefined |
Reading direction used to map ArrowRight and ArrowLeft to expand and collapse. undefined inherits DirectionProvider/LocaleProvider, then "ltr". |
typeahead |
boolean |
true |
Move focus to the next row whose text starts with typed characters. |
typeaheadTimeout |
number |
500 |
Idle time in milliseconds before typeahead starts a new query. |
virtualizer |
TreeVirtualizer |
undefined |
Windowing adapter from useTreeVirtualizer. The tree element becomes the scroll viewport. |
reorder |
TreeReorderController<K> |
undefined |
Drag and Alt+Arrow reorder adapter from useTreeReorder. |
ariaLabel |
string |
undefined |
Accessible name when no visible label or aria-labelledby supplies one. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the tree. |
ariaDescribedby |
string |
undefined |
Space-separated ids that describe the tree. |
Events
| Event | Payload | Description |
|---|---|---|
update:expanded |
[keys: readonly K[]] |
Fired with the next expanded keys whenever expansion changes. |
update:selected |
[keys: readonly K[]] |
Fired with the next selected keys whenever selection changes. |
update:checked |
[keys: readonly K[]] |
Fired with the next normalized checked keys whenever a checkbox changes. |
action |
[key: K, node: T, nativeEvent: Event] |
Fired when Enter or a double click activates a row, for example to open a file. |
load |
[key: K, children: readonly T[]] |
Fired after loadChildren resolves for a node. |
loadError |
[key: K, error: unknown] |
Fired when loadChildren rejects for a node. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
TreeSlotState<T, K> |
TreeItem rows. Receives rendered rows, every visible row, and selection state. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
HTMLDivElement | null |
Rendered tree element. |
id |
string |
Root-owned base id. |
expanded |
readonly K[] |
Currently expanded keys. |
selected |
readonly K[] |
Currently selected keys. |
checked |
readonly K[] |
Normalized checked keys. |
activeKey |
K | null |
Key owning roving focus. |
focus |
(options?: FocusOptions) => void |
Move focus to the active, selected, or first row. |
focusKey |
(key: K, options?: FocusOptions) => boolean |
Make a visible key active and focus it, scrolling a virtual window when needed. |
expand |
(key: K) => Promise<boolean> |
Expand one node, loading lazy children first when needed. |
collapse |
(key: K) => boolean |
Collapse one node. |
toggle |
(key: K) => Promise<boolean> |
Toggle one node's expansion. |
expandAll |
() => boolean |
Expand every node whose children are already resolved. |
collapseAll |
() => boolean |
Collapse every node. |
setSelected |
(keys: readonly K[]) => boolean |
Replace the selected keys. Single mode keeps at most the first key. |
toggleChecked |
(key: K) => boolean |
Toggle one node's checkbox with the configured propagation. |
getCheckedState |
(key: K) => TreeCheckedState |
Resolve the tri-state checkbox value of any known node. |
reload |
(key: K) => Promise<boolean> |
Reload lazy children for one node. |
TreeItem<T, K extends TreeKey>
Source: src/families/data/tree/tree-item.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
item (required) |
TreeFlatNode<T, K> |
required |
Visible row from the TreeRoot slot items. |
ariaLabel |
string |
undefined |
Accessible name when the row text does not supply one. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the row. |
ariaDescribedby |
string |
undefined |
Space-separated ids that describe the row. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
TreeItemSlotState<T, K> |
Row content. Receives the typed node plus expansion, selection, and checkbox state. |
Exposed
| Member | Type | Description |
|---|---|---|
node |
T |
Consumer-owned node data. |
key |
K |
Stable node key. |
level |
number |
One-based depth. |
expandable |
boolean |
Whether the node has, or may lazily load, children. |
expanded |
boolean |
Whether the node is expanded. |
selected |
boolean |
Whether the node is selected. |
checked |
TreeCheckedState |
Tri-state checkbox value. |
disabled |
boolean |
Whether the node or the tree is disabled. |
active |
boolean |
Whether the node owns roving focus. |
loadState |
TreeLoadState |
Lazy children lifecycle for this node. |
state |
TreeItemState |
Stable expansion state token. |
element |
HTMLDivElement | null |
Rendered treeitem element. |
id |
string |
Deterministic treeitem id. |
focus |
(options?: FocusOptions) => void |
Make this row active and focus it. |
TreeItemCheckbox
Source: src/families/data/tree/tree-item-checkbox.vue
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
{ /** Tri-state checkbox value of the owning row. */ readonly checked: TreeCheckedState; /** Whether the owning row or the tree is disabled. */ readonly disabled: boolean; } |
Checkbox indicator content. Receives the tri-state value of the owning row. |
TreeItemToggle
Source: src/families/data/tree/tree-item-toggle.vue
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
{ /** Whether the owning row has, or may lazily load, children. */ readonly expandable: boolean; /** Whether the owning row is expanded. */ readonly expanded: boolean; /** Lazy children lifecycle for the owning row. */ readonly loadState: TreeLoadState; /** Stable expansion state token. */ readonly state: TreeItemState; } |
Expand indicator content, for example a chevron. Receives expansion and load state. |
Behavior
Normative state x input -> outcome table for tree-root.vue, tree-item.vue,
tree-item-toggle.vue, and tree-item-checkbox.vue (@vizejs/ui/tree), plus
the useTreeVirtualizer and useTreeReorder adapters. Every row is proven by
the named test in tree.test.ts, tree-adapters.test.ts, tree-model.test.ts,
or tree-ssr.test.ts; compile-only guarantees live in tree.types.test-d.ts.
The tree follows the WAI-ARIA APG tree view pattern. It renders flat: every
visible node is one role="treeitem" row carrying aria-level,
aria-setsize, and aria-posinset, so DOM order equals visual order and rows
can be virtualized without nested role="group" containers. Nodes are
consumer-owned data of any type T; getKey and getChildren are typed
against T, and the key type K is inferred from getKey and flows into every
v-model, emit, slot, and exposed method.
| ID | State | Input | Outcome | Evidence |
|---|---|---|---|---|
| T1 | default | render | tree with flat treeitem rows, levels, set positions, aria-expanded on parents only, roving tabindex |
renders APG tree semantics with flat levels, set positions, and roving tabindex |
| T2 | focus on a row | ArrowDown/Up/Right/Left, Home, End | APG movement: Right expands then enters, Left collapses then moves to the parent, Home/End jump | arrow keys, Home, and End follow the APG tree keyboard model |
| T3 | focus inside a branch | collapse an ancestor | focus and the active key move to the collapsed ancestor instead of being lost | collapsing an ancestor moves focus from a hidden descendant to the ancestor |
| T4 | dir="rtl" |
ArrowLeft / ArrowRight | horizontal expand and collapse keys swap | RTL maps ArrowLeft to expand and ArrowRight to collapse |
| T5 | single selection | click, Enter, focus movement | click and Enter select one key; Enter and double click emit action; selectionFollowsFocus opt-in |
single selection follows click and Enter, and emits action for activation |
| T6 | multiple selection | click, Shift+click, Space, Shift+Arrow, Ctrl/Cmd+A | toggles, anchor ranges, focus extension, and select-all/clear-all with aria-multiselectable |
multiple selection toggles, extends ranges, and selects all |
| T7 | selectionMode="none" |
click, Enter | no aria-selected, no selection emits; action still fires |
selection mode none omits aria-selected and ignores selection input |
| T8 | checkable cascade |
Space, checkbox click | checks cascade to descendants; ancestors derive checked/mixed/unchecked in aria-checked |
checkboxes cascade to descendants and derive tri-state ancestors |
| T9 | checkPropagation="independent" |
Space | only the requested node toggles | independent checkboxes toggle only the requested node |
| T10 | lazy branch | expand | loadChildren runs once, row is aria-busy while loading, children inherit a checked parent, cached |
lazy children load on expand with busy state, load events, and inherited checks |
| T11 | lazy branch | load rejects, then reload() |
data-load-state="error" and loadError; reload retries and renders children |
failed lazy loads expose an error state and can reload |
| T12 | focus on a row | printable characters | typeahead focuses the next row whose text matches, using the shared typeahead buffer | typeahead moves focus to the next row whose text matches |
| T13 | any | *, expandAll(), collapseAll() |
* expands the focused row's siblings; expand-all covers every resolved branch |
asterisk expands siblings and expandAll/collapseAll cover every resolved branch |
| T14 | disabled row / disabled tree | click, keys, Tab | disabled rows stay focusable but never expand, select, or check; a disabled tree leaves the tab order | disabled rows stay focusable but cannot expand, select, or check |
| T15 | controlled expanded/selected |
user input | update events fire while rendered state waits for the parent | controlled expansion and selection wait for the parent to accept requests |
| T16 | toggle part / expandOnClick |
click | the toggle expands without selecting and keeps focus on the row | the toggle expands on click without selecting, and expandOnClick toggles rows |
| T17 | exposed instance | imperative calls | typed focus, focusKey, expand, toggle, setSelected, toggleChecked, getCheckedState |
exposes typed state and imperative focus, selection, and checkbox controls |
| T18 | missing provider | setup | compound parts fail closed with the shared context diagnostic | compound parts require a matching root provider |
| T19 | useTreeVirtualizer |
render | only the window renders, every row still reports its full-tree set metadata | a virtualizer renders only the window and keeps set metadata for every row |
| T20 | useTreeVirtualizer |
End, typeahead | focus scrolls unmounted rows into the window first; typeahead reaches them through getTextValue |
keyboard focus scrolls rows outside the window into view before focusing them |
| T21 | active row scrolled out | Tab into the tree | the tree element becomes the tab stop and forwards focus to the active row | the tree element takes sequential focus while the active row is outside the window |
| T22 | useTreeReorder |
Alt+ArrowUp/Down/Right/Left | onMove receives sibling, nesting, and outdent requests; data is never mutated by the tree |
Alt+Arrow keys request sibling, nesting, and outdent moves |
| T23 | useTreeReorder with canMove |
move request | vetoed and self-targeted moves are dropped; rows register with the shared pointer sortable engine | canMove vetoes moves and rows register with the pointer sort engine |
| T24 | model helpers | index, flatten, derive, toggle | pre-order indexing, duplicate-key diagnostic, tri-state derivation, disabled-aware cascade | tree-model.test.ts |
| T25 | SSR and hydration | isolated render/mount | byte-identical markup, virtual window from initialRect, hydration without warnings |
tree-ssr.test.ts |
Notes
- Lazy branches that start expanded load after mount, so server and client render the same idle markup before any request runs.
- The toggle and checkbox parts are
aria-hiddenpointer affordances: keyboard users expand with arrows and check with Space on the row itself, which keeps interactive controls from nesting inside atreeitem. - Keys are compared with
Object.is;1and"1"are distinct keys but must not both appear in one tree when reordering, because the drag engine keys rows byString(key). - No styles ship with the primitive. Rows expose
data-state,data-level,data-selected,data-checked,data-load-state,data-dragging, anddata-drop-position; indentation and virtual offsets are consumer-owned.