Tree 
Alpha- composables
- components
- Home
- app.vue
- nuxt.config.ts
Directory Structure
<script setup lang="ts">
import { TreeItem, TreeRoot } from 'radix-vue'
import { Icon } from '@iconify/vue'
const items = [
  {
    title: 'composables',
    icon: 'lucide:folder',
    children: [
      { title: 'useAuth.ts', icon: 'vscode-icons:file-type-typescript' },
      { title: 'useUser.ts', icon: 'vscode-icons:file-type-typescript' },
    ],
  },
  {
    title: 'components',
    icon: 'lucide:folder',
    children: [
      {
        title: 'Home',
        icon: 'lucide:folder',
        children: [
          { title: 'Card.vue', icon: 'vscode-icons:file-type-vue' },
          { title: 'Button.vue', icon: 'vscode-icons:file-type-vue' },
        ],
      },
    ],
  },
  { title: 'app.vue', icon: 'vscode-icons:file-type-vue' },
  { title: 'nuxt.config.ts', icon: 'vscode-icons:file-type-nuxt' },
]
</script>
<template>
  <TreeRoot
    v-slot="{ flattenItems }"
    class="list-none select-none w-56 bg-white text-blackA11 rounded-lg p-2 text-sm font-medium"
    :items="items"
    :get-key="(item) => item.title"
    :default-expanded="['components']"
  >
    <h2 class="font-semibold !text-base text-blackA11 px-2 pt-1">
      Directory Structure
    </h2>
    <TreeItem
      v-for="item in flattenItems"
      v-slot="{ isExpanded }"
      :key="item._id"
      :style="{ 'padding-left': `${item.level - 0.5}rem` }"
      v-bind="item.bind"
      class="flex items-center py-1 px-2 my-0.5 rounded outline-none focus:ring-grass8 focus:ring-2 data-[selected]:bg-grass4"
    >
      <template v-if="item.hasChildren">
        <Icon
          v-if="!isExpanded"
          icon="lucide:folder"
          class="h-4 w-4"
        />
        <Icon
          v-else
          icon="lucide:folder-open"
          class="h-4 w-4"
        />
      </template>
      <Icon
        v-else
        :icon="item.value.icon || 'lucide:file'"
        class="h-4 w-4"
      />
      <div class="pl-2">
        {{ item.value.title }}
      </div>
    </TreeItem>
  </TreeRoot>
</template>Features 
- Can be controlled or uncontrolled.
- Focus is fully managed.
- Full keyboard navigation.
- Supports Right to Left direction.
- Supports multiple selection.
- Different selection behavior.
Installation 
Install the component from your command line.
$ npm add radix-vueAnatomy 
Import all parts and piece them together.
<script setup>
import { TreeItem, TreeRoot, TreeVirtualizer } from 'radix-vue'
</script>
<template>
  <TreeRoot>
    <TreeItem />
    <!-- or with virtual -->
    <TreeVirtualizer>
      <TreeItem />
    </TreeVirtualizer>
  </TreeRoot>
</template>API Reference 
Root 
Contains all the parts of a tree.
| Prop | Default | Type | 
|---|---|---|
| as | 'ul' | AsTag | ComponentThe element or component this component should render as. Can be overwrite by  | 
| asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
| defaultExpanded | string[]The value of the expanded tree when initially rendered. Use when you do not need to control the state of the expanded tree | |
| defaultValue | Record<string, any> | Record<string, any>[]The value of the tree when initially rendered. Use when you do not need to control the state of the tree | |
| dir | 'ltr' | 'rtl'The reading direction of the listbox when applicable.  | |
| disabled | booleanWhen  | |
| expanded | string[]The controlled value of the expanded item. Can be binded-with with  | |
| getKey* | (val: Record<string, any>) => stringThis function is passed the index of each item and should return a unique key for that item | |
| items | Record<string, any>[]List of items | |
| modelValue | Record<string, any> | Record<string, any>[]The controlled value of the tree. Can be binded-with with  | |
| multiple | booleanWhether multiple options can be selected or not. | |
| propagateSelect | booleanWhen  | |
| selectionBehavior | 'toggle' | 'toggle' | 'replace'How multiple selection should behave in the collection. | 
| Emit | Payload | 
|---|---|
| update:expanded | [val: string[]] | 
| update:modelValue | [val: Record<string, any>]Event handler called when the value changes. | 
| Slots (default) | Payload | 
|---|---|
| flattenItems | FlattenedItem<Record<string, any>>[] | 
| modelValue | Record<string, any> | Record<string, any>[] | 
| expanded | string[] | 
Item 
The item component.
| Prop | Default | Type | 
|---|---|---|
| as | 'li' | AsTag | ComponentThe element or component this component should render as. Can be overwrite by  | 
| asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
| level* | numberLevel of depth | |
| value* | Record<string, any>Value given to this item | 
| Emit | Payload | 
|---|---|
| select | [event: SelectEvent<Record<string, any>>]Event handler called when the selecting item.  | 
| toggle | [event: ToggleEvent<Record<string, any>>]Event handler called when the selecting item.  | 
| Slots (default) | Payload | 
|---|---|
| isExpanded | boolean | 
| isSelected | boolean | 
| isIndeterminate | boolean | undefined | 
| handleToggle |  | 
| handleSelect |  | 
| Data Attribute | Value | 
|---|---|
| [data-indent] | Number | 
| [data-expanded] | Present when expanded | 
| [data-selected] | Present when selected | 
Virtualizer 
Virtual container to achieve list virtualization.
| Prop | Default | Type | 
|---|---|---|
| estimateSize | numberEstimated size (in px) of each item | |
| textContent | ((item: Record<string, any>) => string)text content for each item to achieve type-ahead feature | 
| Slots (default) | Payload | 
|---|---|
| item | FlattenedItem<Record<string, any>> | 
Examples 
Selecting multiple items 
The Tree component allows you to select multiple items. You can enable this by providing an array of values instead of a single value.
<script setup lang="ts">
import { ref } from 'vue'
import { TreeRoot } from 'radix-vue'
const people = [
  { id: 1, name: 'Durward Reynolds' },
  { id: 2, name: 'Kenton Towne' },
  { id: 3, name: 'Therese Wunsch' },
  { id: 4, name: 'Benedict Kessler' },
  { id: 5, name: 'Katelyn Rohan' },
]
const selectedPeople = ref([people[0], people[1]])
</script>
<template>
  <TreeRoot
    v-model="selectedPeople"
    multiple
  >
    ...
  </TreeRoot>
</template>Virtual List 
Rendering a long list of item can slow down the app, thus using virtualization would significantly improve the performance.
<script setup lang="ts">
import { ref } from 'vue'
import { TreeItem, TreeRoot, TreeVirtualizer } from 'radix-vue'
</script>
<template>
  <TreeRoot :items>
    <!-- checkout https://radix-vue.com/components/tree.html#virtualizer -->
    <TreeVirtualizer
      v-slot="{ item }"
      :text-content="(opt) => opt.name"
    >
      <TreeItem v-bind="item.bind">
        {{ person.name }}
      </TreeItem>
    </TreeVirtualizer>
  </TreeRoot>
</template>With Checkbox 
Some Tree component might want to show toggled/indeterminate checkbox. We can change the behavior of the Tree component by using a few props and preventDefault event.
We set propagateSelect to true because we want the parent checkbox to select/deselect it's descendants. Then, we add a checkbox that triggers select event.
<script setup lang="ts">
import { ref } from 'vue'
import { TreeItem, TreeRoot } from 'radix-vue'
</script>
<template>
  <TreeRoot
    v-slot="{ flattenItems }"
    :items
    multiple
    propagate-select
  >
    <TreeItem
      v-for="item in flattenItems"
      :key="item._id"
      v-bind="item.bind"
      v-slot="{ handleSelect, isSelected, isIndeterminate }"
      @select="(event) => {
        if (event.detail.originalEvent.type === 'click')
          event.preventDefault()
      }"
      @toggle="(event) => {
        if (event.detail.originalEvent.type === 'keydown')
          event.preventDefault()
      }"
    >
      <Icon
        v-if="item.hasChildren"
        icon="radix-icons:chevron-down"
      />
Nested Tree Node 
The default example shows flatten tree items and nodes, this enables Virtualization and custom feature such as Drag & Drop easier. However, you can also build it to have nested DOM node.
In Tree.vue,
<script setup lang="ts">
import { TreeItem } from 'radix-vue'
interface TreeNode {
  title: string
  icon: string
  children?: TreeNode[]
}
withDefaults(defineProps<{
  treeItems: TreeNode[]
  level?: number
}>(), { level: 0 })
</script>
<template>
  <li
    v-for=" tree in treeItems"
    :key="tree.title"
  >
    <TreeItem
      v-slot="{ isExpanded }"
      as-child
      :level="level"
      :value="tree"
    >
      <button>…</button>
      <ul v-if="isExpanded && tree.children">
        <Tree
          :tree-items="tree.children"
          :level="level + 1"
        />
      </ul>
    </TreeItem>
  </li>
</template>In CustomTree.vue
<template>
  <TreeRoot
    :items="items"
    :get-key="(item) => item.title"
  >
    <Tree :tree-items="items" />
  </TreeRoot>
</template>Draggable/Sortable Tree 
For more complex draggable Tree component, in this example we will be using pragmatic-drag-and-drop, as the core package for handling dnd.
Accessibility 
Adheres to the Tree WAI-ARIA design pattern.
Keyboard Interactions 
| Key | Description | 
|---|---|
| Enter | When highlight on  TreeItem, selects the focused item. | 
| ArrowDown | When focus is on  TreeItem, moves focus to the next item. | 
| ArrowUp | When focus is on  TreeItem, moves focus to the previous item. | 
| ArrowRight | When focus is on a closed  TreeItem(node), it opens the node without moving focus. When on an open node, it moves focus to the first child node. When on an end node, it does nothing. | 
| ArrowLeft | When focus is on an open  TreeItem(node), closes the node. When focus is on a child node that is also either an end node or a closed node, moves focus to its parent node. When focus is on a root node that is also either an end node or a closed node, does nothing. | 
| HomePageUp | Moves focus first  TreeItem | 
| EndPageDown | Moves focus last  TreeItem |