Tree View
A component that is used to show a tree hierarchy.
Anatomy
To set up the tree view component correctly, you'll need to understand its anatomy and how we name its parts.
Each part includes a
data-part
attribute to help identify them in the DOM.
Examples
Learn how to use the TreeView
component in your project. Let's take a look at the most basic
example:
import { TreeView, createTreeCollection } from '@ark-ui/react/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-react'
interface Node {
id: string
name: string
children?: Node[]
}
const collection = createTreeCollection<Node>({
nodeToValue: (node) => node.id,
nodeToString: (node) => node.name,
rootNode: {
id: 'ROOT',
name: '',
children: [
{
id: 'node_modules',
name: 'node_modules',
children: [
{ id: 'node_modules/zag-js', name: 'zag-js' },
{ id: 'node_modules/pandacss', name: 'panda' },
{
id: 'node_modules/@types',
name: '@types',
children: [
{ id: 'node_modules/@types/react', name: 'react' },
{ id: 'node_modules/@types/react-dom', name: 'react-dom' },
],
},
],
},
{
id: 'src',
name: 'src',
children: [
{ id: 'src/app.tsx', name: 'app.tsx' },
{ id: 'src/index.ts', name: 'index.ts' },
],
},
{ id: 'panda.config', name: 'panda.config.ts' },
{ id: 'package.json', name: 'package.json' },
{ id: 'renovate.json', name: 'renovate.json' },
{ id: 'readme.md', name: 'README.md' },
],
},
})
export const Basic = () => {
return (
<TreeView.Root collection={collection}>
<TreeView.Label>Tree</TreeView.Label>
<TreeView.Tree>
{collection.rootNode.children?.map((node, index) => (
<TreeNode key={node.id} node={node} indexPath={[index]} />
))}
</TreeView.Tree>
</TreeView.Root>
)
}
const TreeNode = (props: TreeView.NodeProviderProps<Node>) => {
const { node, indexPath } = props
return (
<TreeView.NodeProvider key={node.id} node={node} indexPath={indexPath}>
{node.children ? (
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchText>
<FolderIcon /> {node.name}
</TreeView.BranchText>
<TreeView.BranchIndicator>
<ChevronRightIcon />
</TreeView.BranchIndicator>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
{node.children.map((child, index) => (
<TreeNode key={child.id} node={child} indexPath={[...indexPath, index]} />
))}
</TreeView.BranchContent>
</TreeView.Branch>
) : (
<TreeView.Item>
<TreeView.ItemIndicator>
<CheckSquareIcon />
</TreeView.ItemIndicator>
<TreeView.ItemText>
<FileIcon />
{node.name}
</TreeView.ItemText>
</TreeView.Item>
)}
</TreeView.NodeProvider>
)
}
import { TreeView, createTreeCollection } from '@ark-ui/solid/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-solid'
import { For, Show } from 'solid-js'
interface Node {
id: string
name: string
children?: Node[]
}
const collection = createTreeCollection<Node>({
nodeToValue: (node) => node.id,
nodeToString: (node) => node.name,
rootNode: {
id: 'ROOT',
name: '',
children: [
{
id: 'node_modules',
name: 'node_modules',
children: [
{ id: 'node_modules/zag-js', name: 'zag-js' },
{ id: 'node_modules/pandacss', name: 'panda' },
{
id: 'node_modules/@types',
name: '@types',
children: [
{ id: 'node_modules/@types/react', name: 'react' },
{ id: 'node_modules/@types/react-dom', name: 'react-dom' },
],
},
],
},
{
id: 'src',
name: 'src',
children: [
{ id: 'src/app.tsx', name: 'app.tsx' },
{ id: 'src/index.ts', name: 'index.ts' },
],
},
{ id: 'panda.config', name: 'panda.config.ts' },
{ id: 'package.json', name: 'package.json' },
{ id: 'renovate.json', name: 'renovate.json' },
{ id: 'readme.md', name: 'README.md' },
],
},
})
export const Basic = () => {
return (
<TreeView.Root collection={collection}>
<TreeView.Label>Tree</TreeView.Label>
<TreeView.Tree>
<For each={collection.rootNode.children}>
{(node, index) => <TreeNode node={node} indexPath={[index()]} />}
</For>
</TreeView.Tree>
</TreeView.Root>
)
}
const TreeNode = (props: TreeView.NodeProviderProps<Node>) => {
const { node, indexPath } = props
return (
<TreeView.NodeProvider node={node} indexPath={indexPath}>
<Show
when={node.children}
fallback={
<TreeView.Item>
<TreeView.ItemIndicator>
<CheckSquareIcon />
</TreeView.ItemIndicator>
<TreeView.ItemText>
<FileIcon />
{node.name}
</TreeView.ItemText>
</TreeView.Item>
}
>
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchText>
<FolderIcon /> {node.name}
</TreeView.BranchText>
<TreeView.BranchIndicator>
<ChevronRightIcon />
</TreeView.BranchIndicator>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
<For each={node.children}>
{(child, index) => <TreeNode node={child} indexPath={[...indexPath, index()]} />}
</For>
</TreeView.BranchContent>
</TreeView.Branch>
</Show>
</TreeView.NodeProvider>
)
}
<script setup lang="ts">
import { TreeView, createTreeCollection } from '@ark-ui/vue/tree-view'
import TreeNode from './tree-node.vue'
interface Node {
id: string
name: string
children?: Node[]
}
const collection = createTreeCollection<Node>({
nodeToValue: (node) => node.id,
nodeToString: (node) => node.name,
rootNode: {
id: 'ROOT',
name: '',
children: [
{
id: 'node_modules',
name: 'node_modules',
children: [
{ id: 'node_modules/zag-js', name: 'zag-js' },
{ id: 'node_modules/pandacss', name: 'panda' },
{
id: 'node_modules/@types',
name: '@types',
children: [
{ id: 'node_modules/@types/react', name: 'react' },
{ id: 'node_modules/@types/react-dom', name: 'react-dom' },
],
},
],
},
{
id: 'src',
name: 'src',
children: [
{ id: 'src/app.tsx', name: 'app.tsx' },
{ id: 'src/index.ts', name: 'index.ts' },
],
},
{ id: 'panda.config', name: 'panda.config.ts' },
{ id: 'package.json', name: 'package.json' },
{ id: 'renovate.json', name: 'renovate.json' },
{ id: 'readme.md', name: 'README.md' },
],
},
})
</script>
<template>
<TreeView.Root :collection="collection">
<TreeView.Label>Tree</TreeView.Label>
<TreeView.Tree>
<TreeNode
v-for="(node, index) in collection.rootNode.children"
:key="node.id"
:node="node"
:indexPath="[index]"
/>
</TreeView.Tree>
</TreeView.Root>
</template>
Using the Root Provider
The RootProvider
component provides a context for the tree-view. It accepts the value of the useTree-view
hook.
You can leverage it to access the component state and methods from outside the tree-view.
import { TreeView, createTreeCollection, useTreeView } from '@ark-ui/react/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-react'
interface Node {
id: string
name: string
children?: Node[]
}
const collection = createTreeCollection<Node>({
nodeToValue: (node) => node.id,
nodeToString: (node) => node.name,
rootNode: {
id: 'ROOT',
name: '',
children: [
{
id: 'node_modules',
name: 'node_modules',
children: [
{ id: 'node_modules/zag-js', name: 'zag-js' },
{ id: 'node_modules/pandacss', name: 'panda' },
{
id: 'node_modules/@types',
name: '@types',
children: [
{ id: 'node_modules/@types/react', name: 'react' },
{ id: 'node_modules/@types/react-dom', name: 'react-dom' },
],
},
],
},
{
id: 'src',
name: 'src',
children: [
{ id: 'src/app.tsx', name: 'app.tsx' },
{ id: 'src/index.ts', name: 'index.ts' },
],
},
{ id: 'panda.config', name: 'panda.config.ts' },
{ id: 'package.json', name: 'package.json' },
{ id: 'renovate.json', name: 'renovate.json' },
{ id: 'readme.md', name: 'README.md' },
],
},
})
export const RootProvider = () => {
const treeView = useTreeView({ collection })
return (
<TreeView.RootProvider value={treeView}>
<TreeView.Label>Tree</TreeView.Label>
<TreeView.Tree>
{collection.rootNode.children?.map((node, index) => (
<TreeNode key={node.id} node={node} indexPath={[index]} />
))}
</TreeView.Tree>
</TreeView.RootProvider>
)
}
const TreeNode = (props: TreeView.NodeProviderProps<Node>) => {
const { node, indexPath } = props
return (
<TreeView.NodeProvider key={node.id} node={node} indexPath={indexPath}>
{node.children ? (
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchText>
<FolderIcon /> {node.name}
</TreeView.BranchText>
<TreeView.BranchIndicator>
<ChevronRightIcon />
</TreeView.BranchIndicator>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
{node.children.map((child, index) => (
<TreeNode key={child.id} node={child} indexPath={[...indexPath, index]} />
))}
</TreeView.BranchContent>
</TreeView.Branch>
) : (
<TreeView.Item>
<TreeView.ItemIndicator>
<CheckSquareIcon />
</TreeView.ItemIndicator>
<TreeView.ItemText>
<FileIcon />
{node.name}
</TreeView.ItemText>
</TreeView.Item>
)}
</TreeView.NodeProvider>
)
}
import { TreeView, createTreeCollection, useTreeView } from '@ark-ui/solid/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-solid'
import { For, Show } from 'solid-js'
interface Node {
id: string
name: string
children?: Node[]
}
const collection = createTreeCollection<Node>({
nodeToValue: (node) => node.id,
nodeToString: (node) => node.name,
rootNode: {
id: 'ROOT',
name: '',
children: [
{
id: 'node_modules',
name: 'node_modules',
children: [
{ id: 'node_modules/zag-js', name: 'zag-js' },
{ id: 'node_modules/pandacss', name: 'panda' },
{
id: 'node_modules/@types',
name: '@types',
children: [
{ id: 'node_modules/@types/react', name: 'react' },
{ id: 'node_modules/@types/react-dom', name: 'react-dom' },
],
},
],
},
{
id: 'src',
name: 'src',
children: [
{ id: 'src/app.tsx', name: 'app.tsx' },
{ id: 'src/index.ts', name: 'index.ts' },
],
},
{ id: 'panda.config', name: 'panda.config.ts' },
{ id: 'package.json', name: 'package.json' },
{ id: 'renovate.json', name: 'renovate.json' },
{ id: 'readme.md', name: 'README.md' },
],
},
})
export const RootProvider = () => {
const treeView = useTreeView({ collection })
return (
<TreeView.RootProvider value={treeView}>
<TreeView.Label>Tree</TreeView.Label>
<TreeView.Tree>
<For each={collection.rootNode.children}>
{(node, index) => <TreeNode node={node} indexPath={[index()]} />}
</For>
</TreeView.Tree>
</TreeView.RootProvider>
)
}
const TreeNode = (props: TreeView.NodeProviderProps<Node>) => {
const { node, indexPath } = props
return (
<TreeView.NodeProvider node={node} indexPath={indexPath}>
<Show
when={node.children}
fallback={
<TreeView.Item>
<TreeView.ItemIndicator>
<CheckSquareIcon />
</TreeView.ItemIndicator>
<TreeView.ItemText>
<FileIcon />
{node.name}
</TreeView.ItemText>
</TreeView.Item>
}
>
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchText>
<FolderIcon /> {node.name}
</TreeView.BranchText>
<TreeView.BranchIndicator>
<ChevronRightIcon />
</TreeView.BranchIndicator>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
<For each={node.children}>
{(child, index) => <TreeNode node={child} indexPath={[...indexPath, index()]} />}
</For>
</TreeView.BranchContent>
</TreeView.Branch>
</Show>
</TreeView.NodeProvider>
)
}
Example not found
If you're using the
RootProvider
component, you don't need to use theRoot
component.
API Reference
Root
Prop | Default | Type |
---|---|---|
collection | TreeCollection<T> The collection of tree nodes | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
defaultExpandedValue | string[] The initial expanded items of the tree view. Use this when you do not need to control the state of the tree view. | |
defaultSelectedValue | string[] The initial selected items of the tree view. Use this when you do not need to control the state of the tree view. | |
expandedValue | string[] The id of the expanded nodes | |
expandOnClick | true | boolean Whether clicking on a branch should open it or not |
focusedValue | string The id of the focused node | |
ids | Partial<{
root: string
tree: string
label: string
node(value: string): string
}> The ids of the tree elements. Useful for composition. | |
lazyMount | false | boolean Whether to enable lazy mounting |
onExpandedChange | (details: ExpandedChangeDetails) => void Called when the tree is opened or closed | |
onFocusChange | (details: FocusChangeDetails) => void Called when the focused node changes | |
onSelectionChange | (details: SelectionChangeDetails) => void Called when the selection changes | |
selectedValue | string[] The id of the selected nodes | |
selectionMode | 'single' | 'multiple' | 'single' Whether the tree supports multiple selection - "single": only one node can be selected - "multiple": multiple nodes can be selected |
typeahead | true | boolean Whether the tree supports typeahead search |
unmountOnExit | false | boolean Whether to unmount on exit. |
BranchContent
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | tree-view |
[data-part] | branch-content |
[data-state] | "open" | "closed" |
[data-depth] | The depth of the item |
[data-path] | The path of the item |
[data-value] | The value of the item |
BranchControl
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | tree-view |
[data-part] | branch-control |
[data-path] | The path of the item |
[data-state] | "open" | "closed" |
[data-disabled] | Present when disabled |
[data-selected] | Present when selected |
[data-focus] | Present when focused |
[data-value] | The value of the item |
[data-depth] | The depth of the item |
BranchIndentGuide
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | tree-view |
[data-part] | branch-indent-guide |
[data-depth] | The depth of the item |
BranchIndicator
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | tree-view |
[data-part] | branch-indicator |
[data-state] | "open" | "closed" |
[data-disabled] | Present when disabled |
[data-selected] | Present when selected |
[data-focus] | Present when focused |
Branch
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | tree-view |
[data-part] | branch |
[data-depth] | The depth of the item |
[data-branch] | |
[data-value] | The value of the item |
[data-path] | The path of the item |
[data-selected] | Present when selected |
[data-state] | "open" | "closed" |
[data-disabled] | Present when disabled |
BranchText
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | tree-view |
[data-part] | branch-text |
[data-disabled] | Present when disabled |
[data-state] | "open" | "closed" |
BranchTrigger
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | tree-view |
[data-part] | branch-trigger |
[data-disabled] | Present when disabled |
[data-state] | "open" | "closed" |
[data-value] | The value of the item |
ItemIndicator
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | tree-view |
[data-part] | item-indicator |
[data-disabled] | Present when disabled |
[data-selected] | Present when selected |
[data-focus] | Present when focused |
Item
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | tree-view |
[data-part] | item |
[data-path] | The path of the item |
[data-value] | The value of the item |
[data-focus] | Present when focused |
[data-selected] | Present when selected |
[data-disabled] | Present when disabled |
[data-depth] | The depth of the item |
ItemText
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Data Attribute | Value |
---|---|
[data-scope] | tree-view |
[data-part] | item-text |
[data-disabled] | Present when disabled |
[data-selected] | Present when selected |
[data-focus] | Present when focused |
Label
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
NodeProvider
Prop | Default | Type |
---|---|---|
indexPath | number[] The index path of the tree node | |
node | NonNullable<T> The tree node |
RootProvider
Prop | Default | Type |
---|---|---|
value | UseTreeViewReturn<T> | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
lazyMount | false | boolean Whether to enable lazy mounting |
unmountOnExit | false | boolean Whether to unmount on exit. |
Tree
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Accessibility
Complies with the Tree View WAI-ARIA design pattern.
Keyboard Support
Key | Description |
---|---|
Tab | Moves focus to the tree view, placing the first tree view item in focus. |
EnterSpace | Selects the item or branch node |
ArrowDown | Moves focus to the next node |
ArrowUp | Moves focus to the previous node |
ArrowRight | When focus is on a closed branch node, opens the branch. When focus is on an open branch node, moves focus to the first item node. |
ArrowLeft | When focus is on an open branch node, closes the node. When focus is on an item or branch node, moves focus to its parent branch node. |
Home | Moves focus to first node without opening or closing a node. |
End | Moves focus to the last node that can be focused without expanding any nodes that are closed. |
a-zA-Z | Focus moves to the next node with a name that starts with the typed character. The search logic ignores nodes that are descendants of closed branch. |
* | Expands all sibling nodes that are at the same depth as the focused node. |
Shift + ArrowDown | Moves focus to and toggles the selection state of the next node. |
Shift + ArrowUp | Moves focus to and toggles the selection state of the previous node. |
Ctrl + A | Selects all nodes in the tree. If all nodes are selected, unselects all nodes. |