Tree View
A component that is used to show a tree hierarchy.
node_modules
zag-js
panda
@types
react
react-dom
src
app.tsx
index.ts
panda.config.ts
package.json
renovate.json
README.md
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
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. |