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'
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>
)
}
interface Node {
id: string
name: string
children?: Node[] | undefined
}
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' },
],
},
})
import { TreeView, createTreeCollection } from '@ark-ui/solid/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-solid'
import { For, Show } from 'solid-js'
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>
)
}
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 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>
<script lang="ts">
import { TreeView, createTreeCollection } from '@ark-ui/svelte/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-svelte'
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>
<TreeView.Root {collection}>
<TreeView.Label>Basic Tree</TreeView.Label>
<TreeView.Tree>
{#each collection.rootNode.children ?? [] as node, index (node.id)}
{@render renderNode(node, [index])}
{/each}
</TreeView.Tree>
</TreeView.Root>
{#snippet renderNode(node: Node, indexPath: number[])}
<TreeView.NodeProvider {node} {indexPath}>
{#if node.children}
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchText>
<FolderIcon />
{node.name}
</TreeView.BranchText>
<TreeView.BranchIndicator>
<ChevronRightIcon />
</TreeView.BranchIndicator>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
{#each node.children as child, index (child.id)}
{@render renderNode(child, [...indexPath, index])}
{/each}
</TreeView.BranchContent>
</TreeView.Branch>
{:else}
<TreeView.Item>
<TreeView.ItemIndicator>
<CheckSquareIcon />
</TreeView.ItemIndicator>
<TreeView.ItemText>
<FileIcon />
{node.name}
</TreeView.ItemText>
</TreeView.Item>
{/if}
</TreeView.NodeProvider>
{/snippet}
Controlled Expanded
Pass the expandedValue
and onExpandedChange
props to the TreeView.Root
component to control the expanded state of
the tree view.
import { TreeView, createTreeCollection } from '@ark-ui/react/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-react'
import { useState } from 'react'
export const ControlledExpanded = () => {
const [expandedValue, setExpandedValue] = useState<string[]>(['node_modules'])
return (
<TreeView.Root
collection={collection}
expandedValue={expandedValue}
onExpandedChange={({ expandedValue }) => setExpandedValue(expandedValue)}
>
<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>
)
}
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' },
],
},
})
import { TreeView, createTreeCollection } from '@ark-ui/solid/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-solid'
import { For, Show, createSignal } from 'solid-js'
export const ControlledExpanded = () => {
const [expandedValue, setExpandedValue] = createSignal<string[]>(['node_modules'])
return (
<TreeView.Root
collection={collection}
expandedValue={expandedValue()}
onExpandedChange={({ expandedValue }) => setExpandedValue(expandedValue)}
>
<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>
)
}
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 setup lang="ts">
import { TreeView, createTreeCollection } from '@ark-ui/vue/tree-view'
import { ref } from 'vue'
import TreeNode from './tree-node.vue'
interface Node {
id: string
name: string
children?: Node[]
}
const expandedValue = ref<string[]>(['node_modules'])
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" v-model:expanded-value="expandedValue">
<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>
<script lang="ts">
import { TreeView, createTreeCollection } from '@ark-ui/svelte/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-svelte'
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: 'src',
name: 'src',
children: [
{ id: 'src/app.tsx', name: 'app.tsx' },
{ id: 'src/index.ts', name: 'index.ts' },
],
},
{ id: 'package.json', name: 'package.json' },
],
},
})
let expandedValue = $state(['node_modules'])
</script>
<TreeView.Root {collection} bind:expandedValue>
<TreeView.Label>Tree</TreeView.Label>
<TreeView.Tree>
{#each collection.rootNode.children ?? [] as node, index (node.id)}
{@render renderNode(node, [index])}
{/each}
</TreeView.Tree>
</TreeView.Root>
{#snippet renderNode(node: Node, indexPath: number[])}
<TreeView.NodeProvider {node} {indexPath}>
{#if node.children}
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchText>
<FolderIcon />
{node.name}
</TreeView.BranchText>
<TreeView.BranchIndicator>
<ChevronRightIcon />
</TreeView.BranchIndicator>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
{#each node.children as child, index (child.id)}
{@render renderNode(child, [...indexPath, index])}
{/each}
</TreeView.BranchContent>
</TreeView.Branch>
{:else}
<TreeView.Item>
<TreeView.ItemIndicator>
<CheckSquareIcon />
</TreeView.ItemIndicator>
<TreeView.ItemText>
<FileIcon />
{node.name}
</TreeView.ItemText>
</TreeView.Item>
{/if}
</TreeView.NodeProvider>
{/snippet}
Controlled Selection
Pass the selectedValue
and onSelectionChange
props to the TreeView.Root
component to control the selected state of
the tree view.
import { TreeView, createTreeCollection } from '@ark-ui/react/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-react'
import { useState } from 'react'
export const ControlledSelected = () => {
const [selectedValue, setSelectedValue] = useState<string[]>(['package.json'])
return (
<TreeView.Root
collection={collection}
selectedValue={selectedValue}
onSelectionChange={({ selectedValue }) => setSelectedValue(selectedValue)}
>
<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>
)
}
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' },
],
},
})
import { TreeView, createTreeCollection } from '@ark-ui/solid/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-solid'
import { For, Show, createSignal } from 'solid-js'
export const ControlledSelected = () => {
const [selectedValue, setSelectedValue] = createSignal<string[]>(['package.json'])
return (
<TreeView.Root
collection={collection}
selectedValue={selectedValue()}
onSelectionChange={({ selectedValue }) => setSelectedValue(selectedValue)}
>
<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>
)
}
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 setup lang="ts">
import { TreeView, createTreeCollection } from '@ark-ui/vue/tree-view'
import { ref } from 'vue'
import TreeNode from './tree-node.vue'
interface Node {
id: string
name: string
children?: Node[]
}
const selectedValue = ref<string[]>(['package.json'])
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" v-model:selected-value="selectedValue">
<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>
<script lang="ts">
import { TreeView, createTreeCollection } from '@ark-ui/svelte/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-svelte'
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: 'src',
name: 'src',
children: [
{ id: 'src/app.tsx', name: 'app.tsx' },
{ id: 'src/index.ts', name: 'index.ts' },
],
},
{ id: 'package.json', name: 'package.json' },
],
},
})
let selectedValue = $state(['package.json'])
</script>
<pre>Selected: {JSON.stringify(selectedValue)}</pre>
<TreeView.Root {collection} bind:selectedValue>
<TreeView.Label>Tree</TreeView.Label>
<TreeView.Tree>
{#each collection.rootNode.children ?? [] as node, index (node.id)}
{@render renderNode(node, [index])}
{/each}
</TreeView.Tree>
</TreeView.Root>
{#snippet renderNode(node: Node, indexPath: number[])}
<TreeView.NodeProvider {node} {indexPath}>
{#if node.children}
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchText>
<FolderIcon />
{node.name}
</TreeView.BranchText>
<TreeView.BranchIndicator>
<ChevronRightIcon />
</TreeView.BranchIndicator>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
{#each node.children as child, index (child.id)}
{@render renderNode(child, [...indexPath, index])}
{/each}
</TreeView.BranchContent>
</TreeView.Branch>
{:else}
<TreeView.Item>
<TreeView.ItemIndicator>
<CheckSquareIcon />
</TreeView.ItemIndicator>
<TreeView.ItemText>
<FileIcon />
{node.name}
</TreeView.ItemText>
</TreeView.Item>
{/if}
</TreeView.NodeProvider>
{/snippet}
Root Provider
Use the useTreeView
hook to create the tree view store and pass it to the TreeView.RootProvider
component. This
allows you to have maximum control over the tree view programmatically.
import { TreeView, createTreeCollection, useTreeView } from '@ark-ui/react/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-react'
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>
)
}
interface Node {
id: string
name: string
children?: Node[] | undefined
}
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' },
],
},
})
import { TreeView, createTreeCollection, useTreeView } from '@ark-ui/solid/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-solid'
import { For, Show } from 'solid-js'
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>
)
}
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 setup lang="ts">
import { TreeView, createTreeCollection, useTreeView } 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' },
],
},
})
const treeView = useTreeView({
collection,
})
</script>
<template>
<TreeView.RootProvider :value="treeView">
<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.RootProvider>
</template>
<script lang="ts">
import { TreeView, createTreeCollection, useTreeView } from '@ark-ui/svelte/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-svelte'
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: 'src',
name: 'src',
children: [
{ id: 'src/app.tsx', name: 'app.tsx' },
{ id: 'src/index.ts', name: 'index.ts' },
],
},
{ id: 'package.json', name: 'package.json' },
],
},
})
const id = $props.id()
const treeView = useTreeView({ collection, id })
</script>
<TreeView.RootProvider value={treeView}>
<TreeView.Label>Root Provider Tree</TreeView.Label>
<TreeView.Tree>
{#each collection.rootNode.children ?? [] as node, index (node.id)}
{@render renderNode(node, [index])}
{/each}
</TreeView.Tree>
</TreeView.RootProvider>
{#snippet renderNode(node: Node, indexPath: number[])}
<TreeView.NodeProvider {node} {indexPath}>
{#if node.children}
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchText>
<FolderIcon />
{node.name}
</TreeView.BranchText>
<TreeView.BranchIndicator>
<ChevronRightIcon />
</TreeView.BranchIndicator>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
{#each node.children as child, index (child.id)}
{@render renderNode(child, [...indexPath, index])}
{/each}
</TreeView.BranchContent>
</TreeView.Branch>
{:else}
<TreeView.Item>
<TreeView.ItemIndicator>
<CheckSquareIcon />
</TreeView.ItemIndicator>
<TreeView.ItemText>
<FileIcon />
{node.name}
</TreeView.ItemText>
</TreeView.Item>
{/if}
</TreeView.NodeProvider>
{/snippet}
If you're using the
RootProvider
component, you don't need to use theRoot
component.
Lazy Loading
Lazy loading is a feature that allows the tree view to load children of a node on demand (or async). This helps to improve the initial load time and memory usage.
To use this, you need to provide the following:
loadChildren
— A function that is used to load the children of a node.onLoadChildrenComplete
— A callback that is called when the children of a node are loaded. Used to update the tree collection.childrenCount
— A number that indicates the number of children of a branch node.
import { TreeView, createTreeCollection } from '@ark-ui/react/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon, Loader2Icon } from 'lucide-react'
import { useState } from 'react'
import { useTreeViewNodeContext } from '../use-tree-view-node-context'
// mock api result
const response: Record<string, Node[]> = {
node_modules: [
{ id: 'zag-js', name: 'zag-js' },
{ id: 'pandacss', name: 'panda' },
{ id: '@types', name: '@types', childrenCount: 2 },
],
'node_modules/@types': [
{ id: 'react', name: 'react' },
{ id: 'react-dom', name: 'react-dom' },
],
src: [
{ id: 'app.tsx', name: 'app.tsx' },
{ id: 'index.ts', name: 'index.ts' },
],
}
// function to load children of a node
function loadChildren(details: TreeView.LoadChildrenDetails<Node>): Promise<Node[]> {
const value = details.valuePath.join('/')
return new Promise((resolve) => {
setTimeout(() => {
resolve(response[value] ?? [])
}, 1200)
})
}
export const AsyncLoading = () => {
const [collection, setCollection] = useState(initialCollection)
return (
<TreeView.Root
collection={collection}
loadChildren={loadChildren}
onLoadChildrenComplete={(e) => setCollection(e.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>
)
}
function TreeNodeIndicator() {
const nodeState = useTreeViewNodeContext()
return nodeState.loading ? <Loader2Icon style={{ animation: 'spin 1s infinite' }} /> : <FolderIcon />
}
const TreeNode = (props: TreeView.NodeProviderProps<Node>) => {
const { node, indexPath } = props
return (
<TreeView.NodeProvider key={node.id} node={node} indexPath={indexPath}>
{node.children || node.childrenCount ? (
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchText>
<TreeNodeIndicator /> {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>
)
}
interface Node {
id: string
name: string
children?: Node[]
childrenCount?: number
}
const initialCollection = createTreeCollection<Node>({
nodeToValue: (node) => node.id,
nodeToString: (node) => node.name,
rootNode: {
id: 'ROOT',
name: '',
children: [
{ id: 'node_modules', name: 'node_modules', childrenCount: 3 },
{ id: 'src', name: 'src', childrenCount: 2 },
{ 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' },
],
},
})
import { TreeView, createTreeCollection } from '@ark-ui/solid/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon, Loader2Icon } from 'lucide-solid'
import { For, createSignal } from 'solid-js'
import { useTreeViewNodeContext } from '../use-tree-view-node-context'
// mock api result
const response: Record<string, Node[]> = {
node_modules: [
{ id: 'zag-js', name: 'zag-js' },
{ id: 'pandacss', name: 'panda' },
{ id: '@types', name: '@types', childrenCount: 2 },
],
'node_modules/@types': [
{ id: 'react', name: 'react' },
{ id: 'react-dom', name: 'react-dom' },
],
src: [
{ id: 'app.tsx', name: 'app.tsx' },
{ id: 'index.ts', name: 'index.ts' },
],
}
// function to load children of a node
function loadChildren(details: TreeView.LoadChildrenDetails<Node>): Promise<Node[]> {
const value = details.valuePath.join('/')
return new Promise((resolve) => {
setTimeout(() => {
resolve(response[value] ?? [])
}, 1200)
})
}
export const AsyncLoading = () => {
const [collection, setCollection] = createSignal(initialCollection)
return (
<TreeView.Root
collection={collection()}
loadChildren={loadChildren}
onLoadChildrenComplete={(e) => setCollection(e.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>
)
}
function TreeNodeIndicator() {
const nodeState = useTreeViewNodeContext()
return nodeState().loading ? <Loader2Icon style={{ animation: 'spin 1s infinite' }} /> : <FolderIcon />
}
const TreeNode = (props: TreeView.NodeProviderProps<Node>) => {
const { node, indexPath } = props
return (
<TreeView.NodeProvider node={node} indexPath={indexPath}>
{node.children || node.childrenCount ? (
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchText>
<TreeNodeIndicator /> {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>
) : (
<TreeView.Item>
<TreeView.ItemIndicator>
<CheckSquareIcon />
</TreeView.ItemIndicator>
<TreeView.ItemText>
<FileIcon />
{node.name}
</TreeView.ItemText>
</TreeView.Item>
)}
</TreeView.NodeProvider>
)
}
interface Node {
id: string
name: string
children?: Node[]
childrenCount?: number
}
const initialCollection = createTreeCollection<Node>({
nodeToValue: (node) => node.id,
nodeToString: (node) => node.name,
rootNode: {
id: 'ROOT',
name: '',
children: [
{ id: 'node_modules', name: 'node_modules', childrenCount: 3 },
{ id: 'src', name: 'src', childrenCount: 2 },
{ 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 setup lang="ts">
import {
type TreeCollection,
TreeView,
type TreeView as TreeViewTypes,
createTreeCollection,
} from '@ark-ui/vue/tree-view'
import { type Ref, ref } from 'vue'
import AsyncTreeNode from './async-tree-node.vue'
interface Node {
id: string
name: string
children?: Node[]
childrenCount?: number
}
// mock api result
const response: Record<string, Node[]> = {
node_modules: [
{ id: 'zag-js', name: 'zag-js' },
{ id: 'pandacss', name: 'panda' },
{ id: '@types', name: '@types', childrenCount: 2 },
],
'node_modules/@types': [
{ id: 'react', name: 'react' },
{ id: 'react-dom', name: 'react-dom' },
],
src: [
{ id: 'app.tsx', name: 'app.tsx' },
{ id: 'index.ts', name: 'index.ts' },
],
}
// function to load children of a node
function loadChildren(details: TreeViewTypes.LoadChildrenDetails<Node>): Promise<Node[]> {
const value = details.valuePath.join('/')
return new Promise((resolve) => {
setTimeout(() => {
resolve(response[value] ?? [])
}, 1200)
})
}
const initialCollection = createTreeCollection<Node>({
nodeToValue: (node) => node.id,
nodeToString: (node) => node.name,
rootNode: {
id: 'ROOT',
name: '',
children: [
{ id: 'node_modules', name: 'node_modules', childrenCount: 3 },
{ id: 'src', name: 'src', childrenCount: 2 },
{ 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' },
],
},
})
const collection = ref(initialCollection) as Ref<TreeCollection<Node>>
</script>
<template>
<TreeView.Root
:collection="collection"
:loadChildren="loadChildren"
@loadChildrenComplete="(e) => (collection = e.collection)"
>
<TreeView.Label>Tree</TreeView.Label>
<TreeView.Tree>
<AsyncTreeNode
v-for="(node, index) in collection.rootNode.children"
:key="node.id"
:node="node"
:indexPath="[index]"
/>
</TreeView.Tree>
</TreeView.Root>
</template>
<script lang="ts">
// biome-ignore lint/style/useImportType: <explanation>
import { TreeView, createTreeCollection } from '@ark-ui/svelte/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon, Loader2Icon } from 'lucide-svelte'
interface Node {
id: string
name: string
children?: Node[]
childrenCount?: number
}
// mock api result
const response: Record<string, Node[]> = {
node_modules: [
{ id: 'zag-js', name: 'zag-js' },
{ id: 'pandacss', name: 'panda' },
{ id: '@types', name: '@types', childrenCount: 2 },
],
'node_modules/@types': [
{ id: 'react', name: 'react' },
{ id: 'react-dom', name: 'react-dom' },
],
src: [
{ id: 'app.tsx', name: 'app.tsx' },
{ id: 'index.ts', name: 'index.ts' },
],
}
// function to load children of a node
function loadChildren(details: TreeView.LoadChildrenDetails<Node>): Promise<Node[]> {
const value = details.valuePath.join('/')
return new Promise((resolve) => {
setTimeout(() => {
resolve(response[value] ?? [])
}, 1200)
})
}
const initialCollection = createTreeCollection<Node>({
nodeToValue: (node) => node.id,
nodeToString: (node) => node.name,
rootNode: {
id: 'ROOT',
name: '',
children: [
{ id: 'node_modules', name: 'node_modules', childrenCount: 3 },
{ id: 'src', name: 'src', childrenCount: 2 },
{ 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' },
],
},
})
let collection = $state(initialCollection)
</script>
<TreeView.Root {collection} {loadChildren} onLoadChildrenComplete={(e) => (collection = e.collection)}>
<TreeView.Label>Tree</TreeView.Label>
<TreeView.Tree>
{#each collection.rootNode.children ?? [] as node, index (node.id)}
{@render renderNode(node, [index])}
{/each}
</TreeView.Tree>
</TreeView.Root>
{#snippet renderNodeIndicator()}
<TreeView.NodeContext>
{#snippet render(nodeState)}
{#if nodeState().loading}
<Loader2Icon style="animation: spin 1s infinite" />
{:else}
<FolderIcon />
{/if}
{/snippet}
</TreeView.NodeContext>
{/snippet}
{#snippet renderNode(node: Node, indexPath: number[])}
<TreeView.NodeProvider {node} {indexPath}>
{#if node.children || node.childrenCount}
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchText>
{@render renderNodeIndicator()}
{node.name}
</TreeView.BranchText>
<TreeView.BranchIndicator>
<ChevronRightIcon />
</TreeView.BranchIndicator>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
{#each node.children ?? [] as child, index (child.id)}
{@render renderNode(child, [...indexPath, index])}
{/each}
</TreeView.BranchContent>
</TreeView.Branch>
{:else}
<TreeView.Item>
<TreeView.ItemIndicator>
<CheckSquareIcon />
</TreeView.ItemIndicator>
<TreeView.ItemText>
<FileIcon />
{node.name}
</TreeView.ItemText>
</TreeView.Item>
{/if}
</TreeView.NodeProvider>
{/snippet}
Lazy Mount
Lazy mounting is a feature that allows the content of a tree view to be rendered only when it is expanded. This is
useful for performance optimization, especially when tree content is large or complex. To enable lazy mounting, use the
lazyMount
prop on the TreeView.Root
component.
In addition, the unmountOnExit
prop can be used in conjunction with lazyMount
to unmount the tree view content when
branches are collapsed, freeing up resources. The next time a branch is expanded, its content will be re-rendered.
import { TreeView, createTreeCollection } from '@ark-ui/react/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-react'
export const LazyMount = () => {
return (
<TreeView.Root collection={collection} lazyMount unmountOnExit>
<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>
)
}
interface Node {
id: string
name: string
children?: Node[] | undefined
}
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' },
],
},
})
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 LazyMount = () => {
return (
<TreeView.Root collection={collection} lazyMount unmountOnExit>
<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" lazy-mount unmount-on-exit>
<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>
<script lang="ts">
import { TreeView, createTreeCollection } from '@ark-ui/svelte/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-svelte'
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>
<TreeView.Root {collection} lazyMount unmountOnExit>
<TreeView.Label>Tree</TreeView.Label>
<TreeView.Tree>
{#each collection.rootNode.children ?? [] as node, index (node.id)}
{@render renderNode(node, [index])}
{/each}
</TreeView.Tree>
</TreeView.Root>
{#snippet renderNode(node: Node, indexPath: number[])}
<TreeView.NodeProvider {node} {indexPath}>
{#if node.children}
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchText>
<FolderIcon />
{node.name}
</TreeView.BranchText>
<TreeView.BranchIndicator>
<ChevronRightIcon />
</TreeView.BranchIndicator>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
{#each node.children as child, index (child.id)}
{@render renderNode(child, [...indexPath, index])}
{/each}
</TreeView.BranchContent>
</TreeView.Branch>
{:else}
<TreeView.Item>
<TreeView.ItemIndicator>
<CheckSquareIcon />
</TreeView.ItemIndicator>
<TreeView.ItemText>
<FileIcon />
{node.name}
</TreeView.ItemText>
</TreeView.Item>
{/if}
</TreeView.NodeProvider>
{/snippet}
Filtering
Filtering is useful when you have a large tree and you want to filter the nodes to only show the ones that match the
search query. Here's an example that composes the filter
method from the TreeCollection
and useFilter
hook to
filter the nodes.
import { useFilter } from '@ark-ui/react/locale'
import { TreeView, createTreeCollection, useTreeViewContext } from '@ark-ui/react/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-react'
import { useState } from 'react'
export const Filtering = () => {
const { contains } = useFilter({ sensitivity: 'base' })
const [collection, setCollection] = useState(initialCollection)
const filter = (value: string) => {
const filtered =
value.length > 0 ? initialCollection.filter((node) => contains(node.name, value)) : initialCollection
setCollection(filtered)
}
return (
<div>
<input placeholder="Search" onChange={(e) => filter(e.target.value)} />
<TreeView.Root collection={collection}>
<TreeView.Tree>
{collection.rootNode.children?.map((node, index) => (
<TreeNode key={node.id} node={node} indexPath={[index]} />
))}
</TreeView.Tree>
</TreeView.Root>
</div>
)
}
const TreeNode = (props: TreeView.NodeProviderProps<Node>) => {
const { node, indexPath } = props
const tree = useTreeViewContext()
const nodeState = tree.getNodeState(props)
return (
<TreeView.NodeProvider key={node.id} node={node} indexPath={indexPath}>
{nodeState.isBranch ? (
<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>
)
}
interface Node {
id: string
name: string
children?: Node[]
}
const initialCollection = 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' },
],
},
})
import { useFilter } from '@ark-ui/solid/locale'
import { TreeView, createTreeCollection } from '@ark-ui/solid/tree-view'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-solid'
import { For, Show, createSignal } from 'solid-js'
export const Filtering = () => {
const filterFn = useFilter({ sensitivity: 'base' })
const [collection, setCollection] = createSignal(initialCollection)
const filter = (value: string) => {
const filtered =
value.length > 0 ? initialCollection.filter((node) => filterFn().contains(node.name, value)) : initialCollection
setCollection(filtered)
}
return (
<div>
<input placeholder="Search" onInput={(e) => filter(e.currentTarget.value)} />
<TreeView.Root collection={collection()}>
<TreeView.Tree>
<For each={collection().rootNode.children}>
{(node, index) => <TreeNode node={node} indexPath={[index()]} />}
</For>
</TreeView.Tree>
</TreeView.Root>
</div>
)
}
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>
)
}
interface Node {
id: string
name: string
children?: Node[]
}
const initialCollection = 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 setup lang="ts">
import { useFilter } from '@ark-ui/vue/locale'
import { TreeView, createTreeCollection } from '@ark-ui/vue/tree-view'
import { shallowRef } from 'vue'
import TreeNode from './tree-node.vue'
interface Node {
id: string
name: string
children?: Node[]
}
const initialCollection = 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' },
],
},
})
const filterFns = useFilter({ sensitivity: 'base' })
const collection = shallowRef(initialCollection)
const filter = (value: string) => {
const filtered =
value.length > 0
? initialCollection.filter((node) => filterFns.value.contains(node.name, value))
: initialCollection
collection.value = filtered
}
</script>
<template>
<div>
<input placeholder="Search" @input="(e) => filter((e.currentTarget as HTMLInputElement).value)" />
<TreeView.Root :collection="collection">
<TreeView.Tree>
<TreeNode
v-for="(node, index) in collection.rootNode.children"
:key="node.id"
:node="node"
:indexPath="[index]"
/>
</TreeView.Tree>
</TreeView.Root>
</div>
</template>
<script lang="ts">
import { TreeView, createTreeCollection } from '@ark-ui/svelte/tree-view'
import { useFilter } from '@ark-ui/svelte/locale'
import { CheckSquareIcon, ChevronRightIcon, FileIcon, FolderIcon } from 'lucide-svelte'
interface Node {
id: string
name: string
children?: Node[]
}
const initialCollection = 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' },
],
},
})
const filterFns = useFilter({ sensitivity: 'base' })
let collection = $state(initialCollection)
const filter = (value: string) => {
const filtered =
value.length > 0 ? initialCollection.filter((node) => filterFns().contains(node.name, value)) : initialCollection
collection = filtered
}
</script>
<div>
<input placeholder="Search" oninput={(e) => filter(e.currentTarget.value)} />
<TreeView.Root {collection}>
<TreeView.Tree>
{#each collection.rootNode.children ?? [] as node, index (node.id)}
{@render renderNode(node, [index])}
{/each}
</TreeView.Tree>
</TreeView.Root>
</div>
{#snippet renderNode(node: Node, indexPath: number[])}
<TreeView.NodeProvider {node} {indexPath}>
{#if node.children}
<TreeView.Branch>
<TreeView.BranchControl>
<TreeView.BranchText>
<FolderIcon />
{node.name}
</TreeView.BranchText>
<TreeView.BranchIndicator>
<ChevronRightIcon />
</TreeView.BranchIndicator>
</TreeView.BranchControl>
<TreeView.BranchContent>
<TreeView.BranchIndentGuide />
{#each node.children as child, index (child.id)}
{@render renderNode(child, [...indexPath, index])}
{/each}
</TreeView.BranchContent>
</TreeView.Branch>
{:else}
<TreeView.Item>
<TreeView.ItemIndicator>
<CheckSquareIcon />
</TreeView.ItemIndicator>
<TreeView.ItemText>
<FileIcon />
{node.name}
</TreeView.ItemText>
</TreeView.Item>
{/if}
</TreeView.NodeProvider>
{/snippet}
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. | |
checkedValue | string[] The controlled checked node value | |
defaultCheckedValue | string[] The initial checked node value when rendered. Use when you don't need to control the checked node value. | |
defaultExpandedValue | string[] The initial expanded node ids when rendered. Use when you don't need to control the expanded node value. | |
defaultFocusedValue | string The initial focused node value when rendered. Use when you don't need to control the focused node value. | |
defaultSelectedValue | string[] The initial selected node value when rendered. Use when you don't need to control the selected node value. | |
expandedValue | string[] The controlled expanded node ids | |
expandOnClick | true | boolean Whether clicking on a branch should open it or not |
focusedValue | string The value 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 |
loadChildren | (details: LoadChildrenDetails<any>) => Promise<any[]> Function to load children for a node asynchronously. When provided, branches will wait for this promise to resolve before expanding. | |
onCheckedChange | (details: CheckedChangeDetails) => void Called when the checked value changes | |
onExpandedChange | (details: ExpandedChangeDetails<any>) => void Called when the tree is opened or closed | |
onFocusChange | (details: FocusChangeDetails<any>) => void Called when the focused node changes | |
onLoadChildrenComplete | (details: LoadChildrenCompleteDetails<any>) => void Called when a node finishes loading children | |
onLoadChildrenError | (details: LoadChildrenErrorDetails<any>) => void Called when loading children fails for one or more nodes | |
onSelectionChange | (details: SelectionChangeDetails<any>) => void Called when the selection changes | |
selectedValue | string[] The controlled selected node value | |
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 |
[data-loading] |
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 |
[data-loading] |
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 |
[data-loading] |
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" |
[data-loading] |
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 |
[data-loading] |
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. |
NodeCheckboxIndicator
Prop | Default | Type |
---|---|---|
fallback | string | number | bigint | boolean | ReactElement<unknown, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<...> | |
indeterminate | string | number | bigint | boolean | ReactElement<unknown, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<...> |
NodeCheckbox
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] | node-checkbox |
[data-state] | "checked" | "unchecked" | "indeterminate" |
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. |