Raccourci clavier : Ctrl + K
Commencer

Arborescence

VTreeView affiche une hiérarchie que le lecteur déplie et replie, comme des fichiers ou un plan de site. Il suit le motif ARIA tree : un seul arrêt de tabulation, les flèches pour se déplacer, ouvrir et fermer, et la saisie pour aller à un libellé.

Utilisation

vue
  • Documents
  • Resume.pdf
  • Notes.txt
<script setup lang="ts">
import { ref } from 'vue'
import { VTreeView } from 'vectis-ui'
import type { TreeItem } from 'vectis-ui'
import { audio_file as audioFile, description, image, picture_as_pdf as pdf } from 'vectis-ui/icons'

const items: TreeItem[] = [
  {
    value: 'documents',
    label: 'Documents',
    children: [
      { value: 'resume', label: 'Resume.pdf', icon: pdf },
      {
        value: 'taxes',
        label: 'Taxes',
        children: [
          { value: 'taxes-2025', label: '2025.pdf', icon: pdf },
          { value: 'taxes-2026', label: '2026.pdf', icon: pdf },
        ],
      },
    ],
  },
  {
    value: 'pictures',
    label: 'Pictures',
    children: [
      { value: 'beach', label: 'Beach.jpg', icon: image },
      { value: 'mountain', label: 'Mountain.jpg', icon: image },
    ],
  },
  {
    value: 'music',
    label: 'Music',
    children: [{ value: 'song', label: 'Song.mp3', icon: audioFile }],
  },
  { value: 'notes', label: 'Notes.txt', icon: description },
]

const expanded = ref(['documents'])
</script>

<template>
  <VTreeView v-model:expanded="expanded" :items="items" label="Files" class="tree" />
</template>

<style scoped>
.tree {
  max-inline-size: 22rem;
}
</style>

Exemples

Sélection

selectionMode="single" sélectionne un nœud au clic, avec Entrée ou Espace, et v-model contient sa valeur. Le chevron replie une branche sans la sélectionner. Un nœud désactivé reste accessible au clavier.

vue
  • Europe
  • Paris
  • Berlin
  • Madrid

Selected: berlin

<script setup lang="ts">
import { ref } from 'vue'
import { VTreeView, VTypography } from 'vectis-ui'
import type { TreeItem, TreeViewModelValue } from 'vectis-ui'

const items: TreeItem[] = [
  {
    value: 'europe',
    label: 'Europe',
    children: [
      { value: 'paris', label: 'Paris' },
      { value: 'berlin', label: 'Berlin' },
      { value: 'madrid', label: 'Madrid', disabled: true },
    ],
  },
  {
    value: 'asia',
    label: 'Asia',
    children: [
      { value: 'tokyo', label: 'Tokyo' },
      { value: 'seoul', label: 'Seoul' },
    ],
  },
]

const selected = ref<TreeViewModelValue>('berlin')
const expanded = ref(['europe'])
</script>

<template>
  <div class="demo">
    <VTreeView
      v-model="selected"
      v-model:expanded="expanded"
      :items="items"
      selection-mode="single"
      label="Offices"
    />
    <VTypography variant="body-sm" tone="muted">Selected: {{ selected ?? 'none' }}</VTypography>
  </div>
</template>

<style scoped>
.demo {
  display: grid;
  gap: var(--vectis-space-3);
  max-inline-size: 22rem;
}
</style>

Cases à cocher

selectionMode="multiple" donne une case à cocher à chaque ligne. Cocher une branche coche tout son sous-arbre, et une branche cochée en partie affiche un tiret. Le modèle liste les nœuds cochés dans l’ordre de l’arbre, une branche comprise dès que tout ce qu’elle contient est coché. Les nœuds désactivés gardent leur état.

vue
  • Content
  • Read
  • Write
  • Publish
  • Billing
  • Invoices
  • Refunds
  • Audit log

Selected: read, write

<script setup lang="ts">
import { computed, ref } from 'vue'
import { VTreeView, VTypography } from 'vectis-ui'
import type { TreeItem, TreeViewModelValue } from 'vectis-ui'

const items: TreeItem[] = [
  {
    value: 'content',
    label: 'Content',
    children: [
      { value: 'read', label: 'Read' },
      { value: 'write', label: 'Write' },
      { value: 'publish', label: 'Publish' },
    ],
  },
  {
    value: 'billing',
    label: 'Billing',
    children: [
      { value: 'invoices', label: 'Invoices' },
      { value: 'refunds', label: 'Refunds', disabled: true },
    ],
  },
  { value: 'audit', label: 'Audit log' },
]

const selected = ref<TreeViewModelValue>(['read', 'write'])
const expanded = ref(['content', 'billing'])
const summary = computed(() => [selected.value ?? []].flat().join(', ') || 'none')
</script>

<template>
  <div class="demo">
    <VTreeView
      v-model="selected"
      v-model:expanded="expanded"
      :items="items"
      selection-mode="multiple"
      label="Permissions"
    />
    <VTypography variant="body-sm" tone="muted">Selected: {{ summary }}</VTypography>
  </div>
</template>

<style scoped>
.demo {
  display: grid;
  gap: var(--vectis-space-3);
  max-inline-size: 22rem;
}
</style>

Un nœud avec href est un lien, et current marque la page consultée. Pour confier la navigation à un routeur, appelez preventDefault() sur l’événement que reçoit activate.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VTreeView } from 'vectis-ui'
import type { TreeItem } from 'vectis-ui'

const items: TreeItem[] = [
  {
    value: 'guides',
    label: 'Guides',
    children: [
      { value: 'installation', label: 'Installation', href: '#links' },
      { value: 'theming', label: 'Theming', href: '#links' },
    ],
  },
  {
    value: 'components',
    label: 'Components',
    children: [
      { value: 'button', label: 'Button', href: '#links' },
      { value: 'tree-view', label: 'Tree view', href: '#links', current: true },
    ],
  },
  { value: 'changelog', label: 'Changelog', href: '#links' },
]

const expanded = ref(['components'])
</script>

<template>
  <VTreeView v-model:expanded="expanded" :items="items" label="Site" size="sm" class="tree" />
</template>

<style scoped>
.tree {
  max-inline-size: 16rem;
}
</style>

Chargement à la demande

loadChildren récupère les enfants d’un nœud lazy à sa première ouverture, et l’arbre les conserve. Un échec referme le nœud et l’annonce ; l’ouverture suivante réessaie.

vue
<script setup lang="ts">
import { VTreeView } from 'vectis-ui'
import type { TreeItem } from 'vectis-ui'

const items: TreeItem[] = [
  { value: 'eu-west', label: 'eu-west', lazy: true },
  { value: 'us-east', label: 'us-east', lazy: true },
]

// Stands in for a request to your server.
function loadChildren(item: TreeItem): Promise<TreeItem[]> {
  return new Promise((resolve) =>
    setTimeout(
      () =>
        resolve(
          [1, 2, 3].map((n) => ({
            value: `${item.value}-${n}`,
            label: `${item.label}-node-${n}`,
            lazy: n === 1,
          })),
        ),
      800,
    ),
  )
}
</script>

<template>
  <VTreeView :items="items" :load-children="loadChildren" label="Servers" class="tree" />
</template>

<style scoped>
.tree {
  max-inline-size: 22rem;
}
</style>

Contenu de fin

Le slot #end ajoute un contenu au bout de chaque ligne, comme un compteur. Il ne doit contenir aucun élément focusable : une ligne est un seul contrôle.

vue
  • Inbox12
  • Work9
  • Family3
  • Drafts
  • Spam41
<script setup lang="ts">
import { ref } from 'vue'
import { VBadge, VTreeView } from 'vectis-ui'
import type { TreeItem } from 'vectis-ui'

const items: TreeItem[] = [
  {
    value: 'inbox',
    label: 'Inbox',
    children: [
      { value: 'work', label: 'Work' },
      { value: 'family', label: 'Family' },
    ],
  },
  { value: 'drafts', label: 'Drafts' },
  { value: 'spam', label: 'Spam' },
]

const unread: Record<string, number> = { inbox: 12, work: 9, family: 3, spam: 41 }
const expanded = ref(['inbox'])
</script>

<template>
  <VTreeView v-model:expanded="expanded" :items="items" label="Mail" class="tree">
    <template #end="{ item }">
      <VBadge v-if="unread[item.value]" :count="unread[item.value]" variant="soft" tone="neutral" />
    </template>
  </VTreeView>
</template>

<style scoped>
.tree {
  max-inline-size: 22rem;
}
</style>

API

Props

PropTypeDéfaut
itemsTreeItem[]aucune
Premier niveau de l’arbre. Chaque nœud a une value unique et un label, et en option icon, children, lazy, href, current et disabled.
selectionModeTreeSelectionMode'none' | 'single' | 'multiple''none'
Si les nœuds peuvent être sélectionnés : pas du tout, un à la fois, ou plusieurs avec des cases à cocher.
loadChildren(item: TreeItem) => Promise<TreeItem[]>aucune
Récupère les enfants d’un nœud lazy à sa première ouverture. Un échec referme le nœud ; l’ouverture suivante réessaie.
sizeTreeViewSize'sm' | 'md''md'
Hauteur des lignes.
labelstringaucune
Nom accessible de l’arbre. Par défaut, celui du dictionnaire de la bibliothèque.
v-modelTreeViewModelValuenull
Sélection : une valeur ou null en mode single, un tableau en mode multiple. Une valeur donnée pour une branche coche son sous-arbre.
v-model:expandedItemValue[][]
Valeurs des nœuds dépliés. Sans liaison, l’arbre garde cet état lui-même.

Événements

ÉvénementType
activate[item: TreeItem, event: MouseEvent | KeyboardEvent]
Un nœud a été cliqué ou validé avec Entrée. Reçoit le nœud et l’événement ; preventDefault() annule un lien.

Slots

SlotType
iconTreeItemSlotProps
Contenu remplaçant l’icône. Reçoit item, level et expanded.
labelTreeItemSlotProps
Contenu remplaçant le libellé. Reçoit les mêmes props.
endTreeItemSlotProps
Contenu au bout de la ligne. Ne doit pas être focusable.

Types

Types utilisés dans les tables d’API. Importez les types exportés depuis vectis-ui.

export interface BuiltinIcon {
  name: string
  paths: readonly [string] | readonly [string, string]
}
export type IconRender =
  | { path: string; viewBox?: string }
  | { component: Component; props?: Record<string, unknown> }
  | { src: string }
  | { text: string; class?: string }
  | { class: string }
export type IconSource = string | BuiltinIcon | IconRender
export type ItemValue = string | number
export interface TreeItem {
  value: ItemValue
  label: string
  icon?: IconSource
  children?: TreeItem[]
  lazy?: boolean
  href?: string
  current?: boolean
  disabled?: boolean
}
export interface TreeItemSlotProps {
  item: TreeItem
  level: number
  expanded: boolean
}
export type TreeViewModelValue = ItemValue | ItemValue[] | null

Variables CSS

TokenValeur
--vectis-control-size-check1.25rem
--vectis-control-size-check-mark0.875rem