---
title: "Tree"
description: "A tree view widget displays a hierarchical list of items that can be expanded or collapsed to show or hide their child items, such as in a file system navigator."
canonical_url: "https://vinicunca.dev/akar/components/tree"
---
# Tree

> A tree view widget displays a hierarchical list of items that can be expanded or collapsed to show or hide their child items, such as in a file system navigator.

`````vue [index.vue]
<script setup lang="ts">
import { akarDemoIcon } from '../akar-demo-icons'
import { TreeItem, TreeRoot } from 'akar'

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>
  <div class="akar-demo akar-demo-Tree">
    <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">
            <PIcon
              v-if="!isExpanded"
              name="i-lucide-folder"
              class="h-4 w-4"
            />
            <PIcon
              v-else
              name="i-lucide-folder-open"
              class="h-4 w-4"
            />
          </template>
          <PIcon
            v-else
            :name="akarDemoIcon(item.value.icon || 'lucide:file')"
            class="h-4 w-4"
          />
          <div class="pl-2">
            {{ item.value.title }}
          </div>
        </TreeItem>
      </TreeRoot>
  </div>
</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.

```bash [pnpm]
pnpm add akar
```

```bash [yarn]
yarn add akar
```

```bash [npm]
npm install akar
```

```bash [bun]
bun add akar
```

## Anatomy

Import all parts and piece them together.

```vue
<script setup>
import { TreeItem, TreeRoot, TreeVirtualizer } from 'akar';
</script>

<template>
  <TreeRoot>
    <TreeItem />

    <!-- or with virtual -->
    <TreeVirtualizer>
      <TreeItem />
    </TreeVirtualizer>
  </TreeRoot>
</template>
```

## API Reference

### Root

Contains all the parts of a tree.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `as` | `"ul"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](/akar/guides/composition) guide for more details. |
| `bubbleSelect` |  | `boolean` | When `true`, selecting children will update the parent state. Requires `multiple` to be `true`. |
| `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` |  | `(M extends true ? U[] : U)` | 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.<br><br>If omitted, inherits globally from `ConfigProvider` or assumes LTR (left-to-right) reading mode. |
| `disabled` |  | `boolean` | When `true`, prevents the user from interacting with tree |
| `expanded` |  | `string[]` | The controlled value of the expanded item. Can be binded with `v-model`. |
| `getChildren` | `val.children` | `((val: T) => T[])` | This function is passed the index of each item and should return a list of children for that item |
| `getKey*` |  | `(val: T): string` | This function is passed the index of each item and should return a unique key for that item |
| `items` |  | `T[]` | List of items |
| `modelValue` |  | `(M extends true ? U[] : U)` | The controlled value of the tree. Can be binded with `v-model`. |
| `multiple` |  | `boolean \| M` | Whether multiple options can be selected or not. |
| `propagateSelect` |  | `boolean` | When `true`, selecting parent will select the descendants. Requires `multiple` to be `true`. |
| `selectionBehavior` | `"toggle"` | `"replace" \| "toggle"` | How multiple selection should behave in the collection. |

**Events**

| Event | Payload | Description |
| --- | --- | --- |
| `update:expanded` | `val: string[]` |  |
| `update:modelValue` | `val: M extends true ? U[] : U` |  |

**Slots**

| Slot | Payload | Description |
| --- | --- | --- |
| `flattenItems` | `FlattenedItem<T>[]` |  |
| `modelValue` | `M extends true ? U[] : U` |  |
| `expanded` | `string[]` |  |

[TreeRoot.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Tree/TreeRoot.vue)

### Item

The item component.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `as` | `"li"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](/akar/guides/composition) guide for more details. |
| `disabled` |  | `boolean` | When `true`, prevents the user from interacting with the item. |
| `level*` |  | `number` | Level of depth |
| `value*` |  | `T` | Value given to this item |

**Events**

| Event | Payload | Description |
| --- | --- | --- |
| `select` | `event: SelectEvent<T>` | Event handler called when the selecting item.<br><br>It can be prevented by calling `event.preventDefault`. |
| `toggle` | `event: ToggleEvent<T>` | Event handler called when the selecting item.<br><br>It can be prevented by calling `event.preventDefault`. |

**Slots**

| Slot | Payload | Description |
| --- | --- | --- |
| `isExpanded` | `boolean` |  |
| `isSelected` | `boolean` |  |
| `isIndeterminate` | `boolean` |  |
| `isDisabled` | `boolean` |  |
| `handleToggle` | `(): void` |  |
| `handleSelect` | `(): void` |  |

[TreeItem.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Tree/TreeItem.vue)

| Data attribute | Value |
| --- | --- |
| `[data-indent]` | Number |
| `[data-expanded]` | Present when expanded |
| `[data-selected]` | Present when selected |

### Virtualizer

Virtual container to achieve list virtualization.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `estimateSize` |  | `number \| ((index: number) => number)` | Estimated size (in px) of each item |
| `overscan` |  | `number` | Number of items rendered outside the visible area |
| `textContent` |  | `((item: Record<string, any>) => string)` | Text content for each item to achieve type-ahead feature |

**Slots**

| Slot | Payload | Description |
| --- | --- | --- |
| `item` | `FlattenedItem<Record<string, any>>` |  |
| `virtualizer` | `Virtualizer<Element \| Window, Element>` |  |
| `virtualItem` | `VirtualItem` |  |

[TreeVirtualizer.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Tree/TreeVirtualizer.vue)

## 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 and set `multiple="true"`.

```vue
<script setup lang="ts">
import { TreeRoot } from 'akar'
import { ref } from '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.

See the [virtualization guide](https://vinicunca.dev/akar/guides/virtualization.md) for more general info on virtualization.

```vue
<script setup lang="ts">
import { TreeItem, TreeRoot, TreeVirtualizer } from 'akar'
import { ref } from 'vue'
</script>

<template>
  <TreeRoot :items>
    <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.

```vue
<script setup lang="ts">
import { TreeItem, TreeRoot } from 'akar'
import { ref } from '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"
      />

      <button
        tabindex="-1"
        @click.stop
        @change="handleSelect"
      >
        <Icon
          v-if="isSelected"
          icon="radix-icons:check"
        />
        <Icon
          v-else-if="isIndeterminate"
          icon="radix-icons:dash"
        />
        <Icon
          v-else
          icon="radix-icons:box"
        />
      </button>

      <div class="pl-2">
        {{ item.value.title }}
      </div>
    </TreeItem>
  </TreeRoot>
</template>
```

### Nested Tree Node

The default example shows flatten tree items and nodes, this enables [Virtualization](https://vinicunca.dev/akar/components/tree.html#virtual-list) and custom feature such as Drag & Drop easier. However, you can also build it to have nested DOM node.

In `Tree.vue`,

```vue
<script setup lang="ts">
import { TreeItem } from 'akar';

interface TreeNode {
  title: string;
  icon: string;
  children?: Array<TreeNode>;
}

withDefaults(defineProps<{
  treeItems: Array<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`

```vue
<template>
  <TreeRoot
    :items="items"
    :get-key="(item) => item.title"
  >
    <Tree :tree-items="items" />
  </TreeRoot>
</template>
```

### Custom children schema

By default, `<TreeRoot />` expects you to provide the list of node's children by passing a list of `children` for every node. You can override that by providing the `getChildren` prop.

> [!NOTE]
> 
> If the node doesn't have any children, `getChildren` should return `undefined` instead of an empty array.

```vue
<script setup lang="ts">
import { TreeRoot } from 'akar'
import { ref } from 'vue'

interface FileNode {
  title: string
  icon: string
}

interface DirectoryNode {
  title: string
  icon: string
  directories?: DirectoryNode[]
  files?: FileNode[]
}
</script>

<template>
  <TreeRoot
    :items="items"
    :get-key="(item) => item.title"
    :get-children="(item) => (!item.files) ? item.directories : (!item.directories) ? item.files : [...item.directories, ...item.files]"
  >
    ...
  </TreeRoot>
</template>
```

### Draggable/Sortable Tree

For more complex draggable `Tree` component, in this example we will be using [pragmatic-drag-and-drop](https://github.com/atlassian/pragmatic-drag-and-drop), as the core package for handling dnd.

[Stackblitz Demo](https://stackblitz.com/edit/github-8f3fzs?file=src%2FTreeDND.vue)

## Accessibility

Adheres to the [Tree WAI-ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/).

### Keyboard Interactions

| Key | Description |
| --- | --- |
| <kbd>Enter</kbd> / <kbd>Space</kbd> | When highlight on `TreeItem`, selects the focused item. |
| <kbd>ArrowDown</kbd> | When focus is on `TreeItem`, moves focus to the next item. |
| <kbd>ArrowUp</kbd> | When focus is on `TreeItem`, moves focus to the previous item. |
| <kbd>ArrowRight</kbd> | 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. |
| <kbd>ArrowLeft</kbd> | 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. |
| <kbd>Home</kbd> / <kbd>PageUp</kbd> | Moves focus first `TreeItem` |
| <kbd>End</kbd> / <kbd>PageDown</kbd> | Moves focus last `TreeItem` |

---

- [ARIA](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/)
- [GitHub](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Tree)
- [npm](https://www.npmjs.com/package/akar)


## Sitemap

See the full [sitemap](https://vinicunca.dev/sitemap.md) for all pages.
