Back to catalog
Component detail entry
Navigation
@vean/ui
STree

Tree

Tree belongs to the Navigation category. Start with demos and usage, then use the API tables to finish integration and styling decisions.

Use the quick jump panel to reach overview, usage, demos, and API first, then continue through the full page.

Quick jump

Related components

If you are composing interactions in the same family, these components usually pair well together.

Tree

Overview

A component for displaying hierarchical tree data with selection and expand/collapse support. STree combines the Aria TreeRoot family of base primitives (TreeRoot/TreeItem/TreeVirtualizerRoot/TreeVirtualizerItem, zero-style) with the TreeRoot context (controlled/uncontrolled selection and expansion, single/multiple selection, cascading selection, Roving Focus keyboard navigation); STreeVirtualizer layers virtualization on top to smoothly handle 1000+ nodes. Node content is fully customized through the item slot — icons, checkboxes, indentation, and connector lines are all injected by the consumer.

Usage

Features

  • 🌳 Hierarchical data model — items: TreeItemData<T>[] (value plus optional recursive children); the TreeItemData<T> generic preserves custom field types
  • 🎛️ Controlled/uncontrolled dual channels — modelValue / defaultValue control selection, expanded / defaultExpanded control expansion; both support v-model and update:* events
  • ☑️ Single and multiple selection — multiple (linked to the modelValue type: single string ↔ array) toggles modes; selectionBehavior: 'toggle' | 'replace' controls multi-select clicks; Shift + Arrow performs contiguous range selection
  • 🧩 Cascading selection — propagateSelect (selecting a parent cascades to descendants), bubbleSelect (a parent auto-selects when all children are selected), allowParentSelect (whether parent nodes can be selected directly)
  • 📂 Expansion strategy — toggleBehavior: 'multiple' | 'single' (multiple expanded nodes by default; single collapses the previous node when expanding a new one, accordion-like)
  • ⌨️ Keyboard navigation — Roving Focus: ↑/↓ move focus, →/← expand/collapse or enter/return to a parent level, Enter/Space select, character typeahead for quick location; loop (default true) wraps around; full RTL support
  • ⚡ Virtualization — STreeVirtualizer + height renders only visible nodes, staying smooth with 1000+ nodes
  • ♿ Accessibility — role="tree"/treeitem with aria-expanded/aria-selected/aria-level/aria-setsize/aria-posinset/aria-multiselectable/aria-disabled, plus data-vean-tree-* data attributes

Component family

  • STree (styled) — entry wrapper; TreeRootProps<T, U, M> generic passthrough + useForwardListeners event merging + top/item/bottom slots; withDefaults mirrors the Aria loop: true default
  • STreeVirtualizer (styled) — virtualized wrapper; forwards contentProps/dynamicContentProps to VirtualizerContent; the item slot additionally exposes virtualItem
  • TreeRoot (Aria) — root component; useControllableState manages selection/expansion, useSelectionBehavior handles single/multi/range selection, useRovingFocusGroup + useTypeahead implement keyboard navigation; provideTreeRootContext bridges child items
  • TreeItem (Aria) — single node; useRovingFocusGroupItem manages focus, renders aria-* and data-* attributes, dispatches select/toggle events via handleAndDispatchCustomEvent
  • TreeVirtualizerRoot (Aria) — virtualized root; VirtualizerRoot + TanStack Virtual, forwards flattened flattenItems
  • TreeVirtualizerItem (Aria) — virtualized node; combines TreeItem + VirtualizerItem (data-vean-tree-virtualizer-item)

Demos

Basic

    Directory Structure

  • components
  • app.vue
  • nuxt.config.ts

Virtualizer

  • 01 Basic — a basic directory tree (items + default-expanded + top slot)
  • 02 Virtualizer — STreeVirtualizer rendering 1000+ nodes with virtualization

API

Aria Layer

6

Tree<T>

3 Slots
Slots
Slot NameParametersDescription
top
—
Content rendered before the tree items.
bottom
—
Content rendered after the tree items.
item
{ item: FlattenedItem<T>; modelValue: MaybeArray<string> | undefined; expanded: string[]; }
Content for a single tree item.

TreeItem

7 Props2 Emits
Props
PropTypeDefaultDescription
value
string
—
Value associated with the current item.
level
number
—
Level.
disabled
boolean
—
When `true`, prevents the user from selecting or toggling the item.
disabledSelect
boolean
—
When `true`, prevents the user from selecting the item.
disabledToggle
boolean
—
When `true`, prevents the user from toggling the item.
asChild
boolean
—
Change the default rendered element for the one passed as a child, merging their props and behavior.
as
AsTag | Component
'div'
The element or component this component should render as. Can be overwrite by `asChild`
Emits
Emit NameParametersDescription
select
TreeSelectEvent<string>
Event handler called when selecting item.
toggle
TreeToggleEvent<string>
Event handler called when toggling item.

TreeRoot<T, U, M>

16 Props2 Emits
Props
PropTypeDefaultDescription
modelValue
U
—
The controlled value of the tree. Can be bound-with with `v-model`.
defaultValue
U
—
The value of the tree when initially rendered. Use when you do not need to control the state of the tree
multiple
IsMultiple<U, M>
—
Determines whether a "single" or "multiple" items can be selected at a time.
items
T[]
—
List of items
expanded
string[]
—
The controlled value of the expanded item. Can be bound-with with `v-model`.
defaultExpanded
string[]
—
The value of the expanded tree when initially rendered.
selectionBehavior
TreeSelectBehavior
—
How multiple selection should behave in the collection.
toggleBehavior
TreeToggleBehavior
'multiple'
Determines whether a "single" or "multiple" items can be toggled at a time.
dir
Direction
—
The reading direction.
loop
boolean
true
When `true`, keyboard navigation will loop from last item to first, and vice versa.
disabled
boolean
—
When `true`, prevents the user from interacting with tree
propagateSelect
boolean
—
When `true`, selecting parent will select the descendants.
bubbleSelect
boolean
—
When `true`, selecting children will update the parent state.
allowParentSelect
boolean
—
When `true`, parent can be selected.
asChild
boolean
—
Change the default rendered element for the one passed as a child, merging their props and behavior.
as
AsTag | Component
'div'
The element or component this component should render as. Can be overwrite by `asChild`
Emits
Emit NameParametersDescription
update:modelValue
M extends true ? string[] : string
Emitted when the model value changes.
update:expanded
string[]
Emitted when the expanded state changes.

TreeVirtualizer<T, U, M>

22 Props2 Emits
Props
PropTypeDefaultDescription
contentProps
VirtualizerContentProps
—
Properties forwarded to the content element.
dynamicContentProps
VirtualizerDynamicContentProps
—
Properties forwarded to the dynamic content element.
animated
boolean
false
When `true`, renders visible items in normal document flow (dynamic mode) so item positions stay correct across expand/collapse without absolute offsets.
modelValue
U
—
The controlled value of the tree. Can be bound-with with `v-model`.
defaultValue
U
—
The value of the tree when initially rendered. Use when you do not need to control the state of the tree
multiple
IsMultiple<U, M>
—
Determines whether a "single" or "multiple" items can be selected at a time.
items
T[]
—
List of items
expanded
string[]
—
The controlled value of the expanded item. Can be bound-with with `v-model`.
defaultExpanded
string[]
—
The value of the expanded tree when initially rendered.
selectionBehavior
TreeSelectBehavior
—
How multiple selection should behave in the collection.
toggleBehavior
TreeToggleBehavior
'multiple'
Determines whether a "single" or "multiple" items can be toggled at a time.
dir
Direction
—
The reading direction.
loop
boolean
true
When `true`, keyboard navigation will loop from last item to first, and vice versa.
disabled
boolean
—
When `true`, prevents the user from interacting with tree
propagateSelect
boolean
—
When `true`, selecting parent will select the descendants.
bubbleSelect
boolean
—
When `true`, selecting children will update the parent state.
allowParentSelect
boolean
—
When `true`, parent can be selected.
asChild
boolean
—
Change the default rendered element for the one passed as a child, merging their props and behavior.
as
AsTag | Component
'div'
The element or component this component should render as. Can be overwrite by `asChild`
height
string | number
—
The height of the virtualizer root
options
VirtualizerOptions
—
Options.
dynamic
boolean
—
Whether dynamic.
Emits
Emit NameParametersDescription
update:modelValue
M extends true ? string[] : string
Emitted when the model value changes.
update:expanded
string[]
Emitted when the expanded state changes.

TreeVirtualizerItem

9 Props2 Emits
Props
PropTypeDefaultDescription
value
string
—
Value associated with the current item.
level
number
—
Level.
disabled
boolean
—
When `true`, prevents the user from selecting or toggling the item.
disabledSelect
boolean
—
When `true`, prevents the user from selecting the item.
disabledToggle
boolean
—
When `true`, prevents the user from toggling the item.
asChild
boolean
—
Change the default rendered element for the one passed as a child, merging their props and behavior.
as
AsTag | Component
'div'
The element or component this component should render as. Can be overwrite by `asChild`
data
VirtualItem
—
Data.
customStyle
boolean
—
Whether custom style.
Emits
Emit NameParametersDescription
select
TreeSelectEvent<string>
Event handler called when selecting item.
toggle
TreeToggleEvent<string>
Event handler called when toggling item.

TreeVirtualizerRoot<T, U, M>

19 Props2 Emits
Props
PropTypeDefaultDescription
modelValue
U
—
The controlled value of the tree. Can be bound-with with `v-model`.
defaultValue
U
—
The value of the tree when initially rendered. Use when you do not need to control the state of the tree
multiple
IsMultiple<U, M>
—
Determines whether a "single" or "multiple" items can be selected at a time.
items
T[]
—
List of items
expanded
string[]
—
The controlled value of the expanded item. Can be bound-with with `v-model`.
defaultExpanded
string[]
—
The value of the expanded tree when initially rendered.
selectionBehavior
TreeSelectBehavior
—
How multiple selection should behave in the collection.
toggleBehavior
TreeToggleBehavior
'multiple'
Determines whether a "single" or "multiple" items can be toggled at a time.
dir
Direction
—
The reading direction.
loop
boolean
true
When `true`, keyboard navigation will loop from last item to first, and vice versa.
disabled
boolean
—
When `true`, prevents the user from interacting with tree
propagateSelect
boolean
—
When `true`, selecting parent will select the descendants.
bubbleSelect
boolean
—
When `true`, selecting children will update the parent state.
allowParentSelect
boolean
—
When `true`, parent can be selected.
asChild
boolean
—
Change the default rendered element for the one passed as a child, merging their props and behavior.
as
AsTag | Component
'div'
The element or component this component should render as. Can be overwrite by `asChild`
height
string | number
—
The height of the virtualizer root
options
VirtualizerOptions
—
Options.
dynamic
boolean
—
Whether dynamic.
Emits
Emit NameParametersDescription
update:modelValue
M extends true ? string[] : string
Emitted when the model value changes.
update:expanded
string[]
Emitted when the expanded state changes.

Notes

Architecture and benchmark differences

TreeRoot owns all state (selection/expansion through the useControllableState controlled/uncontrolled dual channels) and the selection strategy (useSelectionBehavior’s toggle/replace/range selection); all base primitives stay zero-style. STree/STreeVirtualizer only mirror the loop default and pass through slots — the node visuals are fully composed inside the item slot via STreeItem/STreeVirtualizerItem (indentation, icons, checkboxes, focus states all injected through classes). Keyboard navigation builds on Roving Focus (↑/↓ move, →/← expand/collapse, typeahead) and additionally supports Shift range selection — an interaction beyond the default contract of most mainstream tree components. Virtualization builds on the @vean/aria virtualizer and renders only visible nodes.

CapabilityVeanUIAnt DesignElement PlusNaive UI
Aria/style separation✅———
Single/multiple selection (toggle/replace)✅✅✅✅
Expansion strategy (single/multiple toggle)✅✅✅✅
Cascading selection (propagate/bubble/parent)✅✅✅⚠️
Keyboard navigation (arrows + loop + typeahead)✅✅✅⚠️
Virtualization (1k nodes)✅✅⚠️✅
Checkable mode (checkbox)⚠️✅✅✅
Draggable reordering—✅✅✅
Async child loading (loadData)—✅✅✅
Search filtering (searchValue)—✅✅—

⚠️ = partial support (VeanUI checkable can be built via multiple + a custom item slot checkbox icon + the data-selected/data-contains-selected states; Naive UI has no built-in range selection or character location).

Cautions

  • The modelValue type is tied to multiple: a single-string modelValue forces single-select; an array with multiple truthy enables multi-select (enforced by the IsMultiple<U, M> conditional type).
  • The item slot is free composition: the consumer must render STreeItem (or STreeVirtualizerItem) passing item.value/item.level; the slot also exposes item.data (the raw node data) and isExpanded/isSelected/isIndeterminate/hasChildren states.
  • Parent nodes are not selectable by default (allowParentSelect defaults to false); enable it explicitly when needed.
  • propagateSelect/bubbleSelect only take effect in multi-select mode (multiple + array value); with both on, the semi-checked state is expressed via data-contains-selected/isIndeterminate.
  • With toggleBehavior: 'single', expansion is accordion-like — only one branch stays expanded (findParentPath rebuilds the path).
  • loop defaults to true (keyboard focus wraps around); both STree and STreeVirtualizer mirror this default in their wrappers, so no extra work is needed when using the Aria TreeRoot directly.
  • Virtualization requires a height; the STreeVirtualizer item slot additionally provides virtualItem (for :data passthrough and absolute-position styling).
  • Expand and collapse animate with a height-collapsing transition on the moved subtree. STreeVirtualizer has an opt-in animated prop that renders visible items in normal document flow (dynamic mode) so item positions stay correct across expand/collapse without absolute offsets — it is off by default since dynamic measuring is heavier for very large datasets, and while a motion runs the transition list renders fully in document flow before windowing resumes.
  • Node text is fully provided by slots — the component has no hardcoded copy, so no localization is needed.

FAQ

How do I build a checkable tree?

Use multiple with a custom item slot that renders a checkbox icon and consumes the isSelected/isIndeterminate states:

<STree :items="items" multiple>
  <template #item="{ item, isSelected, isIndeterminate }">
    <STreeItem :value="item.value" :level="item.level">
      <SIcon :icon="isIndeterminate ? 'lucide:minus' : isSelected ? 'lucide:check-square' : 'lucide:square'" />
      {{ item.data.label }}
    </STreeItem>
  </template>
</STree>

Add propagateSelect to cascade parent selection to descendants, or bubbleSelect to auto-select a parent when all its children are selected.

How do I control the expansion state?

Controlled: v-model:expanded="expandedKeys" (array of strings); or uncontrolled with :default-expanded="['node-1']". The expansion event is update:expanded.

How do I optimize for large data?

Use STreeVirtualizer with a height: only visible nodes render (customize options.estimateSize for row height), staying smooth with 1000+ nodes.

<STreeVirtualizer height="360px" :items="items">
  <template #item="{ item, virtualItem }">
    <STreeVirtualizerItem :value="item.value" :level="item.level" :data="virtualItem">
      {{ item.data.label }}
    </STreeVirtualizerItem>
  </template>
</STreeVirtualizer>

How do I prevent selecting parent nodes?

allowParentSelect defaults to false — clicking a parent with children only expands/collapses it without selecting. Set :allow-parent-select="true" explicitly to allow selecting parents.

How do I load children asynchronously?

There is no built-in loadData contract; load lazily in the toggle event and update items:

<STree :items="items" @toggle="event => loadChildren(event.value)">
  <!-- item slot -->
</STree>

Write children into the matching node of items once loaded (expansion is tracked by expanded).

How do I customize node icons and indentation?

The item slot composes freely: compute padding-left from item.level, switch icons on item.hasChildren/isExpanded, and style the focus state with focus:ring-* classes — see demo 01.

How does the keyboard work?

↑/↓ move focus across visible nodes; → expands a collapsed node (or enters a child level), ← collapses (or returns to the parent); Enter/Space select the current node; typing characters triggers typeahead location; Shift + ↑/↓ performs range selection in multi-select mode; with loop on (default), focus wraps around.

Changelog

Version history is grouped by release so you can quickly see what changed for this component across published versions.

v0.50.0

2026-10-02

Compare release

Feature

add state-driven expand/collapse motion

Scope: treeAuthors: soybeanfeView commit7bc90

Style

pass virtual items to the animated virtualizer motion branch

Scope: treeAuthors: soybeanfeView commita1bd2

Style

clip virtualizer motion rendering to the visible window

Scope: treeAuthors: soybeanfeView commitda13e

Style

type the STree item slot item as FlattenedItem

Scope: treeAuthors: soybeanfeView commit8eb8b

Style

keep focus through virtualizer motion by unifying the render list

Scope: treeAuthors: soybeanfeView commitfb777

Refactor

remove auto-animate expand transition

Scope: treeAuthors: soybeanfeView commitafcc3

Feature

add state-driven expand/collapse motion

Scope: treeAuthors: soybeanfeView commit7bc90

Fix

pass virtual items to the animated virtualizer motion branch

Scope: treeAuthors: soybeanfeView commita1bd2

Fix

clip virtualizer motion rendering to the visible window

Scope: treeAuthors: soybeanfeView commitda13e

Fix

type the STree item slot item as FlattenedItem

Scope: treeAuthors: soybeanfeView commit8eb8b

Fix

keep focus through virtualizer motion by unifying the render list

Scope: treeAuthors: soybeanfeView commitfb777

Refactor

remove auto-animate expand transition

Scope: treeAuthors: soybeanfeView commitafcc3

v0.0.2

2025-11-18

Compare release

Feature

implement tree component with selection and expansion behavior, including context and hooks for enhanced functionality

Scope: treeAuthors: soybeanjsView commitd6ab7

Feature

enhance tree component with new virtualizer functionality and refactor existing structure for improved usability

Scope: treeAuthors: soybeanjsView commitcf8e7

Feature

add loop prop for keyboard navigation in TreeRoot component

Scope: treeAuthors: soybeanjsView commite9f2a

Feature

enhance tree component with selection behavior and parent path finding functionality

Scope: treeAuthors: soybeanjsView commitbb1ce

Style

remove v-auto-animate directive from Base and Virtualizer examples for cleaner implementation

Scope: treeAuthors: soybeanjsView commit5b391

Feature

implement tree component with selection and expansion behavior, including context and hooks for enhanced functionality

Scope: treeAuthors: soybeanjsView commitd6ab7

Feature

enhance tree component with new virtualizer functionality and refactor existing structure for improved usability

Scope: treeAuthors: soybeanjsView commitcf8e7

Feature

add loop prop for keyboard navigation in TreeRoot component

Scope: treeAuthors: soybeanjsView commite9f2a

Feature

enhance tree component with selection behavior and parent path finding functionality

Scope: treeAuthors: soybeanjsView commitbb1ce

Style

remove v-auto-animate directive from Base and Virtualizer examples for cleaner implementation

Scope: treeAuthors: soybeanjsView commit5b391