Tree
An expandable, selectable hierarchy - file browsers, nested settings, org structures - built on Reka UI's Tree primitive.
Usage
items is a nested array - a node with children renders as an
expandable row, one without renders as a leaf. Click a row to expand it
(and select it, if it's a leaf):
- Documents
- Photos
- Notes.txt
<script setup lang="ts">
const items = [
{
label: 'Documents',
value: 'documents',
children: [
{ label: 'Resume.pdf', value: 'resume' },
{ label: 'Cover Letter.docx', value: 'cover-letter' },
],
},
{ label: 'Notes.txt', value: 'notes' },
]
</script>
<template>
<STree :items="items" />
</template>
Multiple selection
multiple makes v-model an array instead of a single node.
default-expanded (an array of keys) opens branches immediately, no
click needed:
- Frontend
- Vue
- Svelte
- Backend
- Node
- Go
0 selected
<STree v-model="selected" :items="items" multiple :default-expanded="['frontend', 'backend']" />
Checkbox mode
checkbox shows a tri-state checkbox per row (reusing SCheckbox's own
checked/unchecked/indeterminate model) and turns on multiple together
with parent↔child propagation: selecting a parent checks every one of
its descendants, and checking all of a node's children checks the
parent too (partially checking them marks it indeterminate instead):
- Permissions
- Read
- Write
- Admin
<STree v-model="selected" :items="items" checkbox :default-expanded="['permissions']" />
Custom item content
The item slot replaces a row's default icon+label content, scoped
with the node itself plus its live expanded/selected/indeterminate
state:
- Reports
- Daily3
- Weekly12
<STree :items="items">
<template #item="{ item }">
<span class="flex flex-1 items-center justify-between">
<span>{{ item.label }}</span>
<SBadge v-if="item.badge" :label="item.badge" size="sm" variant="soft" />
</span>
</template>
</STree>
Disabled items
A node's own disabled: true blocks just that row - siblings stay
interactive:
- Team
- Members
- Billing
- Danger zone
<STree :items="items" />
const items = [
{ label: 'Members', value: 'members' },
{ label: 'Billing', value: 'billing', disabled: true },
]
Custom :ui
To see exactly what you'd be overriding - the current default classes for
every slot and variant - here's Tree's own theme file:
Props
| Prop | Type | Default |
|---|---|---|
items | TreeItemType[] | - (required) |
modelValue | TreeItemType | TreeItemType[] | - |
defaultValue | TreeItemType | TreeItemType[] | - |
expanded | string[] | - |
defaultExpanded | string[] | - |
multiple | boolean | false |
checkbox | boolean | false |
propagateSelect | boolean | false |
bubbleSelect | boolean | false |
disabled | boolean | false |
getKey | (item: TreeItemType) => string | item => item.value ?? item.label |
getChildren | (item: TreeItemType) => TreeItemType[] | undefined | item => item.children |
size | 'sm' | 'md' | 'lg' | md |
ui | Partial<Record<TreeSlot, string | object>> | - |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | TreeItemType | TreeItemType[] | Fires on selection |
update:expanded | string[] | Fires when a row is expanded or collapsed |
Slots
| Slot | Props | Description |
|---|---|---|
item | { item, level, expanded, selected, indeterminate } | Replaces a row's default icon+label content |