Tree v1.4.2

View source
A tree component built on Nuxt UI Tree, adding search, lazy loading, toolbar, checkboxes, and parent/child strategy.

Introduction

MTree is a thin wrapper over Nuxt UI Tree, fully passing through all its props, events, and slots while adding several enhancements: search filtering with highlight, async lazy loading, a toolbar (expand/collapse toggle, tri-state select all), checkbox multi-select with parent/child strategy (cascade / isolated), key binding (v-model:selectedKeys), an imperative API, and selection categorization. Tree data normalization, filtering, and traversal reuse the Tree utility methods from @movk/core.

Built on Nuxt UI's Tree component — all native props and slots are fully passed through

Usage

Pass items to render a hierarchical structure. Node defaultExpanded controls the initial expansion, and v-model binds the selected node:

<script setup lang="ts">
const items = ref([
  {
    label: 'app',
    icon: 'i-lucide-folder',
    defaultExpanded: true,
    children: [
      { label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
      { label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
    ]
  },
  {
    label: 'composables',
    icon: 'i-lucide-folder',
    children: [
      { label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
      { label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
    ]
  }
])
const value = ref({ label: 'app.vue' })
</script>

<template>
  <MTree v-model="value" :items="items" />
</template>

defaultExpanded Default Expansion

defaultExpanded derives the initially expanded parent nodes from a strategy, falling back to the node's own defaultExpanded flag. Pass true to expand all parents, a number to expand only parents with depth less than that value, or a function for custom logic per node and depth:

<script setup lang="ts">
const items = ref([
  {
    label: 'app',
    icon: 'i-lucide-folder',
    children: [
      {
        label: 'composables',
        icon: 'i-lucide-folder',
        children: [
          { label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
          { label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
        ]
      },
      { label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' }
    ]
  },
  { label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
])
</script>

<template>
  <MTree :items="items" default-expanded />
</template>

searchable Search Filtering

searchable renders a search input at the top, prunes the tree by keyword while preserving ancestor chains for matching nodes. highlight is enabled by default, highlighting matched text and auto-expanding on match. filter allows a custom match predicate. search supports v-model:search two-way binding of the keyword:

<script setup lang="ts">
const items = ref([
  {
    label: 'app',
    icon: 'i-lucide-folder',
    children: [
      { label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
      { label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
    ]
  },
  {
    label: 'components',
    icon: 'i-lucide-folder',
    children: [
      { label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
      { label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
    ]
  }
])
</script>

<template>
  <MTree :items="items" searchable />
</template>

checkable Checkbox Cascade

checkable renders a checkbox before each node, internally enables multiple with parent/child cascade and indeterminate state bubbling. v-model collects the selected node array. Checkboxes coexist with node icon and parent folder icons:

<script setup lang="ts">
const items = ref([
  {
    label: 'app',
    icon: 'i-lucide-folder',
    defaultExpanded: true,
    children: [
      { label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
      { label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
    ]
  },
  {
    label: 'composables',
    icon: 'i-lucide-folder',
    children: [
      { label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
      { label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
    ]
  }
])
const value = ref([])
</script>

<template>
  <MTree v-model="value" :items="items" checkable />
</template>
checkable is syntactic sugar for multiple + strategy (default cascade). Use multiple instead when you need multi-select without rendering checkboxes.

multiple Multiple Selection

multiple enables multi-select without rendering checkboxes. Clicking a node accumulates the selection. v-model collects the selected node array:

<script setup lang="ts">
const items = ref([
  {
    label: 'Tech Center',
    defaultExpanded: true,
    children: [
      { label: 'Frontend', children: [{ label: 'Component Library' }, { label: 'Visualization' }] },
      { label: 'Backend' }
    ]
  },
  { label: 'Product Center', children: [{ label: 'UX Design' }] }
])
const value = ref([])
</script>

<template>
  <MTree v-model="value" :items="items" multiple />
</template>

strategy Parent/Child Strategy

strategy controls the parent/child check relationship in multi-select / checkable mode. cascade (default) cascades parent/child and re-fills the parent when all children are selected. isolated keeps parent and child independent — indeterminate state does not bubble:

<script setup lang="ts">
const items = ref([
  {
    label: 'Tech Center',
    defaultExpanded: true,
    children: [
      { label: 'Frontend', children: [{ label: 'Component Library' }, { label: 'Visualization' }] },
      { label: 'Backend', children: [{ label: 'Gateway' }, { label: 'Storage' }] }
    ]
  }
])
const value = ref([])
</script>

<template>
  <MTree v-model="value" :items="items" checkable strategy="cascade" />
</template>

selectedKeys Key Binding

selectedKeys two-way binds the selection using an array of node keys — useful for re-populating from the backend or syncing with routes. v-model:selectedKeys and v-model are interoperable. Keys are derived from getKey / labelKey:

<script setup lang="ts">
const items = ref([
  {
    label: 'app',
    icon: 'i-lucide-folder',
    defaultExpanded: true,
    children: [
      { label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
      { label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
    ]
  }
])
</script>

<template>
  <MTree v-model:selected-keys="selectedKeys" :items="items" checkable />
</template>

toolbar Toolbar

toolbar renders a top toolbar with expand/collapse toggle buttons. When searchable is set, it embeds a clearable search input. When checkable is set, it adds a tri-state select-all checkbox and selection count:

<script setup lang="ts">
const items = ref([
  {
    label: 'Tech Center',
    children: [
      { label: 'Frontend', children: [{ label: 'Component Library' }, { label: 'Visualization' }] },
      { label: 'Backend', children: [{ label: 'Gateway' }, { label: 'Storage' }] }
    ]
  },
  { label: 'Product Center', children: [{ label: 'UX Design' }, { label: 'User Research' }] }
])
const value = ref([])
</script>

<template>
  <MTree v-model="value" :items="items" toolbar searchable checkable />
</template>
The toolbar's select-all count is based on leaves: in cascade mode, selecting a parent brings in child keys. Counting by leaves avoids double counting.

lazy Async Lazy Loading

lazy works with loadChildren — expanding an unloaded parent node fetches child nodes and displays a loading state. The isLeaf flag on a node marks it as a leaf, preventing expansion placeholder rendering:

<script setup lang="ts">
import type { TreeItem } from '@movk/nuxt'

const items: TreeItem[] = [
  { label: '区域 A' },
  { label: '区域 B' },
  { label: '直辖节点', isLeaf: true }
]

let seq = 0
function loadChildren(node: TreeItem): Promise<TreeItem[]> {
  return new Promise(resolve => setTimeout(() => {
    seq += 1
    resolve([
      { label: `${node.label} / 子节点 ${seq}-1` },
      { label: `${node.label} / 子节点 ${seq}-2`, isLeaf: true }
    ])
  }, 800))
}
</script>

<template>
  <MTree :items="items" lazy :load-children="loadChildren" />
</template>

childrenKey Field Mapping

childrenKey normalizes the backend's child node field to children. labelKey specifies the display field, eliminating the need to pre-transform data structures:

<script setup lang="ts">
const items = ref([
  {
    name: 'Tech Center',
    nodes: [
      { name: 'Frontend', nodes: [{ name: 'Component Library' }, { name: 'Visualization' }] },
      { name: 'Backend' }
    ]
  },
  { name: 'Product Center', nodes: [{ name: 'UX Design' }] }
])
</script>

<template>
  <MTree :items="items" children-key="nodes" label-key="name" />
</template>

labelKey Dot-Path Value Extraction

labelKey specifies the node display field, supporting dot-path deep extraction of nested fields like meta.title. Key derivation, search, and highlight all use the same path:

<script setup lang="ts">
const items = ref([
  {
    meta: { title: 'Tech Center' },
    children: [
      {
        meta: { title: 'Frontend' },
        children: [{ meta: { title: 'Component Library' } }, { meta: { title: 'Visualization' } }]
      },
      { meta: { title: 'Backend' } }
    ]
  },
  { meta: { title: 'Product Center' }, children: [{ meta: { title: 'UX Design' } }] }
])
</script>

<template>
  <MTree :items="items" label-key="meta.title" searchable />
</template>
The dot-path must align with UTree's internal value extraction. Only dot notation (a.b.c) is supported — bracket notation (a[0].b) is not.

virtualize Virtual Scrolling

virtualize passes through Nuxt UI Tree's virtualization capability, rendering only visible nodes — suitable for large data sets:

<script setup lang="ts">
import type { TreeItem } from '@movk/nuxt'

const items: TreeItem[] = Array.from({ length: 60 }, (_, group) => ({
  label: `分组 ${group + 1}`,
  defaultExpanded: group === 0,
  children: Array.from({ length: 30 }, (_, leaf) => ({ label: `节点 ${group + 1}-${leaf + 1}` }))
}))
</script>

<template>
  <MTree :items="items" :virtualize="true" class="max-h-72 w-md" />
</template>

color Primary Color

color passes through Nuxt UI Tree's primary color, applying to selected node text color and keyboard focus ring. The example pre-selects a node for visual reference:

<script setup lang="ts">
const items = ref([
  {
    label: 'app',
    icon: 'i-lucide-folder',
    defaultExpanded: true,
    children: [
      { label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
      { label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
    ]
  },
  {
    label: 'composables',
    icon: 'i-lucide-folder',
    children: [{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' }]
  }
])
const value = ref({ label: 'app.vue' })
</script>

<template>
  <MTree v-model="value" :items="items" color="error" />
</template>
UTree's color only applies to selected node text and focus ring. No visible change occurs when no node is selected. Single-color icons (e.g., lucide) inherit the text color; multi-color icons (e.g., vscode-icons) maintain their own coloring.

trailingIcon Trailing Icon

trailingIcon replaces the expand indicator icon at the end of parent nodes (default i-lucide-chevron-down). item.trailingIcon on node data takes higher priority:

<script setup lang="ts">
const items = ref([
  {
    label: 'app',
    icon: 'i-lucide-folder',
    trailingIcon: 'i-lucide-arrow-down',
    defaultExpanded: true,
    children: [
      { label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
      { label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
    ]
  },
  {
    label: 'composables',
    icon: 'i-lucide-folder',
    children: [{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' }]
  }
])
</script>

<template>
  <MTree :items="items" trailing-icon="i-lucide-chevron-right" />
</template>

expandedIcon Expand Icon

expandedIcon / collapsedIcon customize the leading icon for parent nodes when expanded / collapsed (default i-lucide-folder-open / i-lucide-folder):

<script setup lang="ts">
const items = ref([
  {
    label: 'app',
    defaultExpanded: true,
    children: [
      { label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
      { label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
    ]
  },
  {
    label: 'composables',
    children: [{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' }]
  }
])
</script>

<template>
  <MTree :items="items" expanded-icon="i-lucide-book-open" collapsed-icon="i-lucide-book" />
</template>
item.icon on a node takes higher priority than expandedIcon / collapsedIcon. Therefore, folders that need to switch icons based on expanded state should not set item.icon.

disabled Disabled

disabled disables the entire tree, blocking click events on nodes, toolbar controls, and checkboxes for expand, collapse, and select operations. Per-node item.disabled disables that node and its entire subtree — freezing expansion state and disabling checkboxes within the subtree:

<script setup lang="ts">
const items = ref([
  {
    label: 'app',
    icon: 'i-lucide-folder',
    defaultExpanded: true,
    children: [
      { label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
      { label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
    ]
  },
  {
    label: 'composables',
    icon: 'i-lucide-folder',
    children: [{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' }]
  }
])
const value = ref([{ label: 'useAuth.ts' }])
</script>

<template>
  <MTree v-model="value" :items="items" checkable toolbar disabled />
</template>
Imperative API methods (expandToDepth, selectAll, etc.) are explicit calls and are not affected by disabled. disabled only intercepts user clicks and toolbar interactions.

Examples

Custom Node

Customize node content via passed-through slots like item-trailing. Uncovered slots are still rendered by Nuxt UI Tree's defaults:

<script setup lang="ts">
import type { TreeItem } from '@movk/nuxt'

const items: TreeItem[] = [
  {
    label: 'app',
    icon: 'i-lucide-folder',
    defaultExpanded: true,
    children: [
      { label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
      { label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
    ]
  }
]
</script>

<template>
  <MTree :items="items">
    <template #item-trailing="{ item }">
      <UBadge v-if="!item.children" label="file" color="neutral" variant="subtle" size="sm" />
    </template>
  </MTree>
</template>

Custom Toolbar

toolbar-leading / toolbar-trailing append content to the start/end of the default toolbar. Use the #toolbar slot for full control — its scope exposes methods and state like toggleExpand, selectAll, clear, and selectionSummary:

部门
0 项
<script setup lang="ts">
import type { TreeItem } from '@movk/nuxt'

const items: TreeItem[] = [
  {
    label: '技术中心',
    children: [
      { label: '前端组', children: [{ label: '组件库' }, { label: '可视化' }] },
      { label: '后端组', children: [{ label: '网关' }, { label: '存储' }] }
    ]
  },
  { label: '产品中心', children: [{ label: '交互设计' }, { label: '用户研究' }] }
]

const checked = ref<TreeItem[]>([])
</script>

<template>
  <MTree v-model="checked" :items="items" toolbar searchable checkable>
    <template #toolbar-leading>
      <UBadge label="部门" color="neutral" variant="subtle" size="sm" />
    </template>
    <template #toolbar-trailing>
      <UBadge :label="`${checked.length} 项`" color="primary" variant="subtle" size="sm" />
    </template>
  </MTree>
</template>

Imperative Control

Obtain the component instance via useTemplateRef and call methods like expandToDepth, collapseAll, selectAll, and clearSelection to control the tree:

<script setup lang="ts">
import type { TreeExposed, TreeItem } from '@movk/nuxt'

const items: TreeItem[] = [
  {
    label: '技术中心',
    children: [
      { label: '前端组', children: [{ label: '组件库' }, { label: '可视化' }] },
      { label: '后端组', children: [{ label: '网关' }, { label: '存储' }] }
    ]
  },
  { label: '产品中心', children: [{ label: '交互设计' }, { label: '用户研究' }] }
]

const checked = ref<TreeItem[]>([])
const tree = useTemplateRef<TreeExposed>('tree')
</script>

<template>
  <div class="space-y-3">
    <div class="flex flex-wrap gap-2">
      <UButton size="xs" label="展开到第 2 层" @click="tree?.expandToDepth(2)" />
      <UButton size="xs" label="收起全部" color="neutral" variant="subtle" @click="tree?.collapseAll()" />
      <UButton size="xs" label="全选" @click="tree?.selectAll()" />
      <UButton size="xs" label="清空" color="neutral" variant="subtle" @click="tree?.clearSelection()" />
    </div>
    <MTree ref="tree" v-model="checked" :items="items" checkable />
  </div>
</template>

Selection Categorization

The instance's reactive treeSelection returns selection categories: leaves (selected leaves), parents (fully selected parents), halfSelected (indeterminate parents), and strictlyChecked (excludes children selected via parent cascade):

叶子(leaves):无
满选父级(parents):无
半选父级(halfSelected):无
<script setup lang="ts">
import type { TreeExposed, TreeItem } from '@movk/nuxt'

const items: TreeItem[] = [
  {
    label: '技术中心',
    defaultExpanded: true,
    children: [
      { label: '前端组', children: [{ label: '组件库' }, { label: '可视化' }] },
      { label: '后端组', children: [{ label: '网关' }, { label: '存储' }] }
    ]
  }
]

const checked = ref<TreeItem[]>([])
const tree = useTemplateRef<TreeExposed>('tree')

const labels = (nodes?: TreeItem[]) => (nodes ?? []).map(node => node.label).join('') || ''
</script>

<template>
  <div class="space-y-3">
    <MTree ref="tree" v-model="checked" :items="items" checkable />
    <dl class="text-sm text-muted space-y-1">
      <div>叶子(leaves):{{ labels(tree?.treeSelection.leaves) }}</div>
      <div>满选父级(parents):{{ labels(tree?.treeSelection.parents) }}</div>
      <div>半选父级(halfSelected):{{ labels(tree?.treeSelection.halfSelected) }}</div>
    </dl>
  </div>
</template>

API

Props

Prop Default Type
as'ul'any

The element or component this component should render as.

itemsT

树数据源,按 childrenKey 解析层级

childrenKey'children'string

取子节点数组的字段名,归一化为 UTree 的 children

labelKey'label'keyof Extract<NestedItem<T>, object> & string | DotPathKeys<Extract<NestedItem<T>, object>>

展示字段名

getKey(val: T[number]) => string

自定义节点 key,缺省取 labelKey 字段值

defaultExpandednumber | false | true | (node: T[number], depth: number): boolean

初始展开策略,缺省回退节点上的 defaultExpanded 标记

  • true 展开全部父级
  • number 展开 depth 小于该值的父级
  • 函数 按节点与深度自定义
selectedKeysstring[]

选中节点 key 列表,可用 v-model:selectedKeys 双向绑定

multipleM

开启多选

strategy'cascade'"cascade" | "isolated"

多选 / checkable 下的父子勾选策略

  • 'cascade' 父子级联(propagateSelect + bubbleSelect)
  • 'isolated' 父子互不关联
size'md'"md" | "xs" | "sm" | "lg" | "xl"

尺寸

color'primary'"primary" | "secondary" | "info" | "success" | "warning" | "error" | "important" | "neutral"

主色

expandedIcon取 app.config 的 ui.icons.folderOpenany

父节点展开时的图标

collapsedIcon取 app.config 的 ui.icons.folderany

父节点折叠时的图标

filterTreeFilter<T[number]>

自定义匹配谓词,缺省按 labelKey 文本不区分大小写包含匹配

loadChildrenTreeLoadChildren<T[number]>

懒加载回调,展开未加载的父节点时调用

checkableM

渲染复选框并启用多选(multiple + strategy,默认 cascade)

trailingIconappConfig.ui.icons.chevronDownany

The icon displayed on the right side of a parent node.

modelValueM extends true ? T[number][] : T[number]

The controlled value of the Tree. Can be bind as v-model.

defaultValueM extends true ? T[number][] : T[number]

The value of the Tree when initially rendered. Use when you do not need to control the state of the Tree.

virtualizefalseboolean | { overscan?: number; estimateSize?: number | ((index: number) => number); }

Enable virtualization for large lists. Note: when enabled, the tree structure is flattened like if nested was set to false.

onSelect(e: SelectEvent<T[number]>, item: T[number]) => void
onToggle(e: ToggleEvent<T[number]>, item: T[number]) => void
expanded[]string[]

The controlled value of the expanded item. Can be binded with v-model.

selectionBehavior"replace" | "toggle"

How multiple selection should behave in the collection.

search''string
propagateSelectboolean

选中父节点时级联选中子节点。cascade 策略下默认开启,显式 true 可在 isolated 下强制开启;关闭级联请用 strategy='isolated'

bubbleSelectboolean

子节点全部选中时回填父节点。cascade 策略下默认开启,显式 true 可在 isolated 下强制开启;关闭级联请用 strategy='isolated'

disabledboolean

禁用整棵树,阻断点击、工具栏与复选框的展开、折叠、选中

searchableboolean

开启顶部搜索过滤

highlighttrueboolean

高亮命中文本,仅在 searchable 时生效

lazyboolean

开启异步懒加载子节点

toolbarboolean

开启顶部工具栏(展开/折叠,checkable 时附带全选/清空)

nestedtrueboolean

Use nested DOM structure (children inside parents) vs flattened structure (all items at same level). When virtualize is enabled, this is automatically set to false.

uiRecord<string, ClassNameValue> & { root?: SlotClass; item?: SlotClass; listWithChildren?: SlotClass; itemWithChildren?: SlotClass; link?: SlotClass; linkLeadingIcon?: SlotClass; linkLabel?: SlotClass; linkTrailing?: SlotClass; linkTrailingIcon?: SlotClass; container?: SlotClass; toolbar?: SlotClass; toolbarButton?: SlotClass; search?: SlotClass; checkbox?: SlotClass; highlight?: SlotClass; loading?: SlotClass; loadingIcon?: SlotClass; empty?: SlotClass; }

Emits

Event Type
update:modelValue[val: M extends true ? T[number][] : T[number]]
update:expanded[val: string[]]
update:search[value: string]
change[payload: { value: M extends true ? T[number][] : T[number]; keys: string[]; selection: TreeSelectionResult<T[number]>; }]
update:selectedKeys[value: string[]]
In addition to passing through Nuxt UI Tree's update:modelValue and update:expanded, MTree additionally provides:
  • update:search: Fires when the search keyword changes, supports v-model:search.
  • update:selectedKeys: Fires when the selected key list changes, supports v-model:selectedKeys.
  • change: Fires when the selection changes. Payload is { value, keys, selection }. keys is derived from getKey/labelKey. selection is the selection categorization result.

Slots

Slot Type
item-wrapper{ item: T[number]; index: number; level: number; expanded: boolean; selected: boolean; indeterminate: boolean; handleSelect: () => void; handleToggle: () => void; ui: { root: (props?: Record<string, any>) => string; item: (props?: Record<string, any>) => string; listWithChildren: (props?: Record<string, any>) => string; itemWithChildren: (props?: Record<string, any>) => string; link: (props?: Record<string, any>) => string; linkLeadingIcon: (props?: Record<string, any>) => string; linkLabel: (props?: Record<string, any>) => string; linkTrailing: (props?: Record<string, any>) => string; linkTrailingIcon: (props?: Record<string, any>) => string; }; }
item{ item: T[number]; index: number; level: number; expanded: boolean; selected: boolean; indeterminate: boolean; handleSelect: () => void; handleToggle: () => void; ui: { root: (props?: Record<string, any>) => string; item: (props?: Record<string, any>) => string; listWithChildren: (props?: Record<string, any>) => string; itemWithChildren: (props?: Record<string, any>) => string; link: (props?: Record<string, any>) => string; linkLeadingIcon: (props?: Record<string, any>) => string; linkLabel: (props?: Record<string, any>) => string; linkTrailing: (props?: Record<string, any>) => string; linkTrailingIcon: (props?: Record<string, any>) => string; }; }
item-leading{ item: T[number]; index: number; level: number; expanded: boolean; selected: boolean; indeterminate: boolean; handleSelect: () => void; handleToggle: () => void; ui: { root: (props?: Record<string, any>) => string; item: (props?: Record<string, any>) => string; listWithChildren: (props?: Record<string, any>) => string; itemWithChildren: (props?: Record<string, any>) => string; link: (props?: Record<string, any>) => string; linkLeadingIcon: (props?: Record<string, any>) => string; linkLabel: (props?: Record<string, any>) => string; linkTrailing: (props?: Record<string, any>) => string; linkTrailingIcon: (props?: Record<string, any>) => string; }; }
item-label{ item: T[number]; index: number; level: number; expanded: boolean; selected: boolean; indeterminate: boolean; handleSelect: () => void; handleToggle: () => void; ui: { root: (props?: Record<string, any>) => string; item: (props?: Record<string, any>) => string; listWithChildren: (props?: Record<string, any>) => string; itemWithChildren: (props?: Record<string, any>) => string; link: (props?: Record<string, any>) => string; linkLeadingIcon: (props?: Record<string, any>) => string; linkLabel: (props?: Record<string, any>) => string; linkTrailing: (props?: Record<string, any>) => string; linkTrailingIcon: (props?: Record<string, any>) => string; }; }
item-trailing{ item: T[number]; index: number; level: number; expanded: boolean; selected: boolean; indeterminate: boolean; handleSelect: () => void; handleToggle: () => void; ui: { root: (props?: Record<string, any>) => string; item: (props?: Record<string, any>) => string; listWithChildren: (props?: Record<string, any>) => string; itemWithChildren: (props?: Record<string, any>) => string; link: (props?: Record<string, any>) => string; linkLeadingIcon: (props?: Record<string, any>) => string; linkLabel: (props?: Record<string, any>) => string; linkTrailing: (props?: Record<string, any>) => string; linkTrailingIcon: (props?: Record<string, any>) => string; }; }
toolbar{ expandAll: () => void; collapseAll: () => void; toggleExpand: () => void; allExpanded: boolean; selectAll: () => void; clear: () => void; search: string; disabled?: boolean; selectionSummary: TreeSelectionSummary; }
toolbar-leadingany

默认工具栏起始处追加内容

toolbar-trailingany

默认工具栏末尾追加内容

emptyany
loading{ node: T[number]; }

Expose

You can access the typed component instance via useTemplateRef.

NameType
expandAll()void

Expand all expandable nodes

collapseAll()void

Collapse all nodes

expandToDepth(depth)void

Expand to the specified depth. depth=0 collapses all

selectAll()void

Select all selectable nodes

clearSelection()void

Clear all selections

treeSelectionTreeSelectionResult

Current selection categorization (selected / leaves / parents / halfSelected / strictlyChecked)

Theme

app.config.ts
export default defineAppConfig({
  ui: {
    tree: {
      slots: {
        container: 'flex flex-col gap-2 min-h-0',
        toolbar: 'flex items-center gap-1.5',
        toolbarButton: 'shrink-0',
        search: 'flex-1 min-w-0',
        checkbox: 'shrink-0 me-1.5',
        highlight: 'rounded-[2px] bg-primary/15 text-primary',
        loading: 'flex items-center gap-1.5 text-sm text-muted',
        loadingIcon: 'size-4 shrink-0 animate-spin',
        empty: 'py-6 text-center text-sm text-muted'
      }
    }
  }
})

Changelog

No recent changes
Copyright © 2025 - 2026 YiXuan - MIT License