Tree
- composables
- components
- Home
- app.vue
- nuxt.config.ts
Directory Structure
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 reka-uiAnatomy
Import all parts and piece them together.
<script setup>
import { TreeItem, TreeRoot, TreeVirtualizer } from 'reka-ui'
</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 overwritten 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 | |
getChildren | val.children | ((val: Record<string, any>) => Record<string, any>[])This function is passed the index of each item and should return a list of children for that item |
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 of the toggle 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 overwritten 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 | |
overscan | numberNumber of items rendered outside the visible area | |
textContent | ((item: Record<string, any>) => string)Text content for each item to achieve type-ahead feature |
| Slots (default) | Payload |
|---|---|
item | FlattenedItem<Record<string, any>> |
virtualizer | Virtualizer<Element | Window, Element> |
virtualItem | VirtualItem |
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 'reka-ui'
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 'reka-ui'
</script>
<template>
<TreeRoot :items>
<!-- checkout https://reka-ui.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 'reka-ui'
</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 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 'reka-ui'
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>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. :::
<script setup lang="ts">
import { ref } from 'vue'
import { TreeRoot } from 'reka-ui'
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, 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 |
