Tree View
The TreeView component provides a hierarchical view of data, similar to a file system explorer. It allows users to expand and collapse branches, select individual or multiple nodes, and traverse the hierarchy using keyboard navigation.
My Documents
Features
- Display hierarchical data in a tree structure
- Expand or collapse nodes
- Support for keyboard navigation
- Select single or multiple nodes (depending on the selection mode)
- Perform actions on the nodes, such as deleting them or performing some other operation
Installation
To use the tree view machine in your project, run the following command in your command line:
npm install @zag-js/tree-view @zag-js/react # or yarn add @zag-js/tree-view @zag-js/react
npm install @zag-js/tree-view @zag-js/solid # or yarn add @zag-js/tree-view @zag-js/solid
npm install @zag-js/tree-view @zag-js/vue # or yarn add @zag-js/tree-view @zag-js/vue
Anatomy
To set up the tree view correctly, you'll need to understand its anatomy.
Usage
First, import the tree view package into your project
import * as tree from "@zag-js/tree-view"
The tree view package exports two key functions:
- machine— The state machine logic for the tree view widget.
- connect— The function that translates the machine's state to JSX attributes and event handlers.
Next, import the required hooks and functions for your framework and use the tree view machine in your project 🔥
Create the tree collection
Use the collection function to create a tree collection. This create a tree
factory that the component uses for traversal.
import * as tree from "@zag-js/tree-view" interface Node { id: string name: string children?: Node[] } const collection = tree.collection<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" }, ], }, ], }, ], }, })
Create the tree view
Pass the tree collection to the machine to create the tree view.
import { normalizeProps, useMachine } from "@zag-js/react" import * as tree from "@zag-js/tree-view" import { FileIcon, FolderIcon, ChevronRightIcon } from "lucide-react" import { useId } from "react" // 1. Create the tree collection interface Node { id: string name: string children?: Node[] } const collection = tree.collection<Node>({ // ... }) // 2. Create the recursive tree node interface TreeNodeProps { node: Node indexPath: number[] api: tree.Api } const TreeNode = (props: TreeNodeProps): JSX.Element => { const { node, indexPath, api } = props const nodeProps = { indexPath, node } const nodeState = api.getNodeState(nodeProps) if (nodeState.isBranch) { return ( <div {...api.getBranchProps(nodeProps)}> <div {...api.getBranchControlProps(nodeProps)}> <FolderIcon /> <span {...api.getBranchTextProps(nodeProps)}>{node.name}</span> <span {...api.getBranchIndicatorProps(nodeProps)}> <ChevronRightIcon /> </span> </div> <div {...api.getBranchContentProps(nodeProps)}> <div {...api.getBranchIndentGuideProps(nodeProps)} /> {node.children?.map((childNode, index) => ( <TreeNode key={childNode.id} node={childNode} indexPath={[...indexPath, index]} api={api} /> ))} </div> </div> ) } return ( <div {...api.getItemProps(nodeProps)}> <FileIcon /> {node.name} </div> ) } // 3. Create the tree view export function TreeView() { const service = useMachine(tree.machine, { id: useId(), collection }) const api = tree.connect(service, normalizeProps) return ( <div {...api.getRootProps()}> <h3 {...api.getLabelProps()}>My Documents</h3> <div {...api.getTreeProps()}> {collection.rootNode.children?.map((node, index) => ( <TreeNode key={node.id} node={node} indexPath={[index]} api={api} /> ))} </div> </div> ) }
import { normalizeProps, useMachine } from "@zag-js/solid" import * as tree from "@zag-js/tree-view" import { ChevronRightIcon, FileIcon, FolderIcon } from "lucide-solid" import { Accessor, createMemo, createUniqueId, Index, JSX, Show, } from "solid-js" // 1. Create the tree collection interface Node { id: string name: string children?: Node[] } const collection = tree.collection<Node>({ // ... }) // 2. Create the recursive tree node interface TreeNodeProps { node: Node indexPath: number[] api: Accessor<tree.Api> } const TreeNode = (props: TreeNodeProps): JSX.Element => { const { node, indexPath, api } = props const nodeProps = { indexPath, node } const nodeState = createMemo(() => api().getNodeState(nodeProps)) return ( <Show when={nodeState().isBranch} fallback={ <div {...api().getItemProps(nodeProps)}> <FileIcon /> {node.name} </div> } > <div {...api().getBranchProps(nodeProps)}> <div {...api().getBranchControlProps(nodeProps)}> <FolderIcon /> <span {...api().getBranchTextProps(nodeProps)}>{node.name}</span> <span {...api().getBranchIndicatorProps(nodeProps)}> <ChevronRightIcon /> </span> </div> <div {...api().getBranchContentProps(nodeProps)}> <div {...api().getBranchIndentGuideProps(nodeProps)} /> <Index each={node.children}> {(childNode, index) => ( <TreeNode node={childNode()} indexPath={[...indexPath, index]} api={api} /> )} </Index> </div> </div> </Show> ) } // 3. Create the tree view export function TreeView() { const service = useMachine(tree.machine, { id: createUniqueId(), collection }) const api = createMemo(() => tree.connect(service, normalizeProps)) return ( <div {...api().getRootProps()}> <h3 {...api().getLabelProps()}>My Documents</h3> <div {...api().getTreeProps()}> <Index each={collection.rootNode.children}> {(node, index) => ( <TreeNode node={node()} indexPath={[index]} api={api} /> )} </Index> </div> </div> ) }
<!-- TreeNode.vue --> <script setup lang="ts"> import { FileIcon, FolderIcon, ChevronRightIcon } from "lucide-vue-next" import type { Api } from "@zag-js/tree-view" interface Node { id: string name: string children?: Node[] } interface Props { node: Node indexPath: number[] api: Api } const props = defineProps<Props>() const nodeProps = computed(() => ({ indexPath: props.indexPath, node: props.node, })) const nodeState = computed(() => props.api.getNodeState(nodeProps.value)) </script> <template> <template v-if="nodeState.isBranch"> <div v-bind="api.getBranchProps(nodeProps)"> <div v-bind="api.getBranchControlProps(nodeProps)"> <FolderIcon /> <span v-bind="api.getBranchTextProps(nodeProps)">{{ node.name }}</span> <span v-bind="api.getBranchIndicatorProps(nodeProps)"> <ChevronRightIcon /> </span> </div> <div v-bind="api.getBranchContentProps(nodeProps)"> <div v-bind="api.getBranchIndentGuideProps(nodeProps)" /> <TreeNode v-for="(childNode, index) in node.children" :key="childNode.id" :node="childNode" :index-path="[...indexPath, index]" :api="api" /> </div> </div> </template> <template v-else> <div v-bind="api.getItemProps(nodeProps)"><FileIcon /> {{ node.name }}</div> </template> </template>
<!-- TreeView.vue --> <script setup lang="ts"> import * as tree from "@zag-js/tree-view" import { normalizeProps, useMachine } from "@zag-js/vue" import { computed, useId } from "vue" // 1. Create the tree collection interface Node { id: string name: string children?: Node[] } const collection = tree.collection<Node>({ // ... }) const service = useMachine(tree.machine, { id: useId(), collection }) const api = computed(() => tree.connect(service, normalizeProps)) </script> <template> <main class="tree-view"> <div v-bind="api.getRootProps()"> <h3 v-bind="api.getLabelProps()">My Documents</h3> <div v-bind="api.getTreeProps()"> <TreeNode v-for="(node, index) in api.collection.rootNode.children" :key="node.id" :node="node" :index-path="[index]" :api="api" /> </div> </div> </main> </template>
Expanding and Collapsing Nodes
By default, the tree view will expand or collapse when clicking the branch
control. To control the expanded state of the tree view, use the api.expand
and api.collapse methods.
api.expand(["node_modules/pandacss"]) // expand a single node api.expand() // expand all nodes api.collapse(["node_modules/pandacss"]) // collapse a single node api.collapse() // collapse all nodes
Multiple selection
The tree view supports multiple selection. To enable this, set the
selectionMode to multiple.
const service = useMachine(tree.machine, { selectionMode: "multiple", })
Setting the default expanded nodes
To set the default expanded nodes, use the expandedValue context property.
const service = useMachine(tree.machine, { defaultExpandedValue: ["node_modules/pandacss"], })
Setting the default selected nodes
To set the default selected nodes, use the selectedValue context property.
const service = useMachine(tree.machine, { defaultSelectedValue: ["node_modules/pandacss"], })
Indentation Guide
When rendering a branch node in the tree view, you can render the indentGuide
element by using the api.getBranchIndentGuideProps() function.
<div {...api.getBranchProps(nodeProps)}> <div {...api.getBranchControlProps(nodeProps)}> <FolderIcon /> {node.name} <span {...api.getBranchIndicatorProps(nodeProps)}> <ChevronRightIcon /> </span> </div> <div {...api.getBranchContentProps(nodeProps)}> <div {...api.getBranchIndentGuideProps(nodeProps)} /> {node.children.map((childNode, index) => ( <TreeNode key={childNode.id} node={childNode} indexPath={[...indexPath, index]} api={api} /> ))} </div> </div>
Listening for selection
When a node is selected, the onSelectionChange callback is invoked with the
selected nodes.
const service = useMachine(tree.machine, { onSelectionChange(details) { console.log("selected nodes:", details) }, })
Listening for expanding and collapsing
When a node is expanded or collapsed, the onExpandedChange callback is invoked
with the expanded nodes.
const service = useMachine(tree.machine, { onExpandedChange(details) { console.log("expanded nodes:", details) }, })
Lazy Loading
Added in v1.15.0
Lazy loading is a feature that allows the tree view to load children of a node on demand. 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.
function TreeAsync() { const [collection, setCollection] = useState( tree.collection({ 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 }, ], }, }), ) const service = useMachine(tree.machine, { id: useId(), collection, async loadChildren({ valuePath, signal }) { const url = `/api/file-system/${valuePath.join("/")}` const response = await fetch(url, { signal }) const data = await response.json() return data.children }, onLoadChildrenComplete({ collection }) { setCollection(collection) }, }) // ... }
Methods and Properties
Machine Context
The tree view machine exposes the following context properties:
- collection- TreeCollection<T>The tree collection data
- ids- Partial<{ root: string; tree: string; label: string; node(value: string): string; }>The ids of the tree elements. Useful for composition.
- expandedValue- string[]The controlled expanded node ids
- defaultExpandedValue- string[]The initial expanded node ids when rendered. Use when you don't need to control the expanded node value.
- selectedValue- string[]The controlled selected node value
- defaultSelectedValue- string[]The initial selected node value when rendered. Use when you don't need to control the selected node value.
- defaultCheckedValue- string[]The initial checked node value when rendered. Use when you don't need to control the checked node value.
- checkedValue- string[]The controlled checked node value
- defaultFocusedValue- stringThe initial focused node value when rendered. Use when you don't need to control the focused node value.
- focusedValue- stringThe value of the focused node
- selectionMode- "single" | "multiple"Whether the tree supports multiple selection - "single": only one node can be selected - "multiple": multiple nodes can be selected
- onExpandedChange- (details: ExpandedChangeDetails) => voidCalled when the tree is opened or closed
- onSelectionChange- (details: SelectionChangeDetails) => voidCalled when the selection changes
- onFocusChange- (details: FocusChangeDetails) => voidCalled when the focused node changes
- onCheckedChange- (details: CheckedChangeDetails) => voidCalled when the checked value changes
- onLoadChildrenComplete- (details: LoadChildrenCompleteDetails<T>) => voidCalled when a node finishes loading children
- onLoadChildrenError- (details: LoadChildrenErrorDetails<T>) => voidCalled when loading children fails for one or more nodes
- expandOnClick- booleanWhether clicking on a branch should open it or not
- typeahead- booleanWhether the tree supports typeahead search
- loadChildren- (details: LoadChildrenDetails<T>) => Promise<T[]>Function to load children for a node asynchronously. When provided, branches will wait for this promise to resolve before expanding.
- dir- "ltr" | "rtl"The document's text/writing direction.
- id- stringThe unique identifier of the machine.
- getRootNode- () => ShadowRoot | Node | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
Machine API
The tree view api exposes the following methods:
- collection- TreeCollection<V>The tree collection data
- expandedValue- string[]The value of the expanded nodes
- setExpandedValue- (value: string[]) => voidFunction to set the expanded value
- selectedValue- string[]The value of the selected nodes
- setSelectedValue- (value: string[]) => voidFunction to set the selected value
- checkedValue- string[]The value of the checked nodes
- toggleChecked- (value: string, isBranch: boolean) => voidFunction to toggle the checked value of a node
- setChecked- (value: string[]) => voidFunction to set the checked value of a node
- clearChecked- () => voidFunction to clear the checked value of a node
- getCheckedMap- () => CheckedValueMapFunction to get the checked details of branch and leaf nodes
- getVisibleNodes- () => V[]Function to get the visible nodes
- expand- (value?: string[]) => voidFunction to expand nodes. If no value is provided, all nodes will be expanded
- collapse- (value?: string[]) => voidFunction to collapse nodes If no value is provided, all nodes will be collapsed
- select- (value?: string[]) => voidFunction to select nodes If no value is provided, all nodes will be selected
- deselect- (value?: string[]) => voidFunction to deselect nodes If no value is provided, all nodes will be deselected
- focus- (value: string) => voidFunction to focus a node by value
- selectParent- (value: string) => voidFunction to select the parent node of the focused node
- expandParent- (value: string) => voidFunction to expand the parent node of the focused node
Data Attributes
Accessibility
Adheres to the Tree View WAI-ARIA design pattern.
Keyboard Interactions
- TabMoves focus to the tree view, placing the first tree view item in focus.
- EnterSpaceSelects the item or branch node
- ArrowDownMoves focus to the next node
- ArrowUpMoves focus to the previous node
- ArrowRightWhen 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.
- ArrowLeftWhen 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.
- HomeMoves focus to first node without opening or closing a node.
- EndMoves focus to the last node that can be focused without expanding any nodes that are closed.
- a-zA-ZFocus 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 + ArrowDownMoves focus to and toggles the selection state of the next node.
- Shift + ArrowUpMoves focus to and toggles the selection state of the previous node.
- Ctrl + ASelects all nodes in the tree. If all nodes are selected, unselects all nodes.
Edit this page on GitHub