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
This command will install the framework agnostic tree view logic and the reactive utilities for your framework of choice.
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 [state, send] = useMachine(tree.machine({ id: useId(), collection })) const api = tree.connect(state, send, 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 [state, send] = useMachine( tree.machine({ id: createUniqueId(), collection }), ) const api = createMemo(() => tree.connect(state, send, 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 [state, send] = useMachine(tree.machine({ id: useId(), collection })) const api = computed(() => tree.connect(state.value, send, 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 [state, send] = useMachine( tree.machine({ selectionMode: "multiple", }), )
Setting the default expanded nodes
To set the default expanded nodes, use the expandedValue
context property.
const [state, send] = useMachine( tree.machine({ expandedValue: ["node_modules/pandacss"], }), )
Setting the default selected nodes
To set the default selected nodes, use the selectedValue
context property.
const [state, send] = useMachine( tree.machine({ selectedValue: ["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 [state, send] = 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 [state, send] = useMachine( tree.machine({ onExpandedChange(details) { console.log("expanded nodes:", details) }, }), )
Methods and Properties
Machine Context
The tree view machine exposes the following context properties:
collection
TreeCollection<T>
The tree collection dataids
Partial<{ root: string; tree: string; label: string; node(value: string): string; }>
The ids of the tree elements. Useful for composition.expandedValue
string[]
The id of the expanded nodesselectedValue
string[]
The id of the selected nodesfocusedValue
string
The id of the focused nodeselectionMode
"single" | "multiple"
Whether the tree supports multiple selection - "single": only one node can be selected - "multiple": multiple nodes can be selectedonExpandedChange
(details: ExpandedChangeDetails) => void
Called when the tree is opened or closedonSelectionChange
(details: SelectionChangeDetails) => void
Called when the selection changesonFocusChange
(details: FocusChangeDetails) => void
Called when the focused node changesexpandOnClick
boolean
Whether clicking on a branch should open it or nottypeahead
boolean
Whether the tree supports typeahead searchdir
"ltr" | "rtl"
The document's text/writing direction.id
string
The unique identifier of the machine.getRootNode
() => ShadowRoot | Node | Document
A 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 dataexpandedValue
string[]
The id of the expanded nodessetExpandedValue
(value: string[]) => void
Function to set the expanded valueselectedValue
string[]
The id of the selected nodessetSelectedValue
(value: string[]) => void
Function to set the selected valuegetVisibleNodes
() => V[]
Function to get the visible nodesexpand
(value?: string[]) => void
Function to expand nodes. If no value is provided, all nodes will be expandedcollapse
(value?: string[]) => void
Function to collapse nodes If no value is provided, all nodes will be collapsedselect
(value?: string[]) => void
Function to select nodes If no value is provided, all nodes will be selecteddeselect
(value?: string[]) => void
Function to deselect nodes If no value is provided, all nodes will be deselectedfocus
(value: string) => void
Function to focus an item nodeselectParent
(value: string) => void
Function to select the parent node of the focused nodeexpandParent
(value: string) => void
Function 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