Pallas UI
DocsComponents
Core Concepts
    • Introduction
    • Getting Started
    • Theming
    • Color Tokens
    • Spacing & Sizing
    • Layout Guide
    • AspectRatio
    • Box
    • Flex
    • Grid
    • Shapes
Previews
    • Accordion
    • Alert
    • Avatar
    • Badge
    • Breadcrumb
    • Button
    • Carousel
    • Checkbox
    • Combobox
    • Command
    • Date Picker
    • Form
    • Input
    • Input OTP
    • Label
    • MenuBar
    • Modal
    • Popover
    • Progress
    • Radio Group
    • ScrollArea
    • Segmented
    • Select
    • Separator
    • Sheet
    • Sidebar
    • Skeleton
    • Slider
    • Spinner
    • Steps
    • Switch
    • Tabs
    • Textarea
    • Toast
    • Tooltip
    • TreeView
    • Typography
  1. Components
  2. TreeView

TreeView

A component for displaying hierarchical data in an expandable tree format, with support for selection, checkboxes, inline renaming, and keyboard navigation.

Loading preview...

Installation

Install the following dependencies

npm install @ark-ui/react

Copy and paste the following code into your project

'use client'
 
import { TreeView } from '@ark-ui/react/tree-view'
import { type Assign, type WithFixedClassName, createStyleContext } from '@pallas-ui/style-context'
import type { ComponentRef } from 'react'
 
import { type TreeViewVariantProps, treeView } from '@styled-system/recipes'
import type { ComponentProps, JsxStyleProps } from '@styled-system/types'
 
const { withProvider, withContext } = createStyleContext(treeView)
 
export type RootProps = WithFixedClassName<ComponentProps<typeof TreeView.Root>>
 
export const Root = withProvider<
  ComponentRef<typeof TreeView.Root>,
  Assign<RootProps, TreeViewVariantProps & JsxStyleProps>
>(TreeView.Root, 'root')
 
export const Label = withContext<
  ComponentRef<typeof TreeView.Label>,
  Assign<ComponentProps<typeof TreeView.Label>, JsxStyleProps>
>(TreeView.Label, 'label')
 
export const Tree = withContext<
  ComponentRef<typeof TreeView.Tree>,
  Assign<ComponentProps<typeof TreeView.Tree>, JsxStyleProps>
>(TreeView.Tree, 'tree')
 
export const Branch = withContext<
  ComponentRef<typeof TreeView.Branch>,
  Assign<ComponentProps<typeof TreeView.Branch>, JsxStyleProps>
>(TreeView.Branch, 'branch')
 
export const BranchControl = withContext<
  ComponentRef<typeof TreeView.BranchControl>,
  Assign<ComponentProps<typeof TreeView.BranchControl>, JsxStyleProps>
>(TreeView.BranchControl, 'branchControl')
 
export const BranchTrigger = withContext<
  ComponentRef<typeof TreeView.BranchTrigger>,
  Assign<ComponentProps<typeof TreeView.BranchTrigger>, JsxStyleProps>
>(TreeView.BranchTrigger, 'branchTrigger')
 
export const BranchIndicator = withContext<
  ComponentRef<typeof TreeView.BranchIndicator>,
  Assign<ComponentProps<typeof TreeView.BranchIndicator>, JsxStyleProps>
>(TreeView.BranchIndicator, 'branchIndicator')
 
export const BranchText = withContext<
  ComponentRef<typeof TreeView.BranchText>,
  Assign<ComponentProps<typeof TreeView.BranchText>, JsxStyleProps>
>(TreeView.BranchText, 'branchText')
 
export const BranchContent = withContext<
  ComponentRef<typeof TreeView.BranchContent>,
  Assign<ComponentProps<typeof TreeView.BranchContent>, JsxStyleProps>
>(TreeView.BranchContent, 'branchContent')
 
export const BranchIndentGuide = withContext<
  ComponentRef<typeof TreeView.BranchIndentGuide>,
  Assign<ComponentProps<typeof TreeView.BranchIndentGuide>, JsxStyleProps>
>(TreeView.BranchIndentGuide, 'branchIndentGuide')
 
export const Item = withContext<
  ComponentRef<typeof TreeView.Item>,
  Assign<ComponentProps<typeof TreeView.Item>, JsxStyleProps>
>(TreeView.Item, 'item')
 
export const ItemText = withContext<
  ComponentRef<typeof TreeView.ItemText>,
  Assign<ComponentProps<typeof TreeView.ItemText>, JsxStyleProps>
>(TreeView.ItemText, 'itemText')
 
export const ItemIndicator = withContext<
  ComponentRef<typeof TreeView.ItemIndicator>,
  Assign<ComponentProps<typeof TreeView.ItemIndicator>, JsxStyleProps>
>(TreeView.ItemIndicator, 'itemIndicator')
 
export const NodeProvider = TreeView.NodeProvider
export const NodeContext = TreeView.NodeContext
 
export const NodeCheckbox = withContext<
  ComponentRef<typeof TreeView.NodeCheckbox>,
  Assign<ComponentProps<typeof TreeView.NodeCheckbox>, JsxStyleProps>
>(TreeView.NodeCheckbox, 'nodeCheckbox')
 
export const NodeCheckboxIndicator = withContext<
  ComponentRef<typeof TreeView.NodeCheckboxIndicator>,
  Assign<ComponentProps<typeof TreeView.NodeCheckboxIndicator>, JsxStyleProps>
>(TreeView.NodeCheckboxIndicator, 'nodeCheckboxIndicator')
 
export const NodeRenameInput = withContext<
  ComponentRef<typeof TreeView.NodeRenameInput>,
  Assign<ComponentProps<typeof TreeView.NodeRenameInput>, JsxStyleProps>
>(TreeView.NodeRenameInput, 'nodeRenameInput')
 
const TreeViewComponent = {
  Root,
  Label,
  Tree,
  NodeProvider,
  NodeContext,
  Branch,
  BranchControl,
  BranchTrigger,
  BranchIndicator,
  BranchText,
  BranchContent,
  BranchIndentGuide,
  Item,
  ItemText,
  ItemIndicator,
  NodeCheckbox,
  NodeCheckboxIndicator,
  NodeRenameInput,
}
 
export { createTreeCollection } from '@ark-ui/react/tree-view'
export default TreeViewComponent

Update the import paths to match your project setup

Usage

import TreeView, { createTreeCollection } from '@/components/ui/tree-view'

Use createTreeCollection to transform your data into a tree structure the component understands. Then wrap the output in TreeView.Root and render each node using TreeView.NodeProvider.

interface FileNode {
  id: string
  name: string
  children?: FileNode[]
}
 
const collection = createTreeCollection<FileNode>({
  nodeToValue: (node) => node.id,
  nodeToString: (node) => node.name,
  nodeToChildren: (node) => node.children ?? [],
  rootNode: {
    id: 'ROOT',
    name: '',
    children: [
      {
        id: 'src',
        name: 'src',
        children: [
          { id: 'src/index', name: 'index.tsx' },
        ],
      },
    ],
  },
})
 
<TreeView.Root collection={collection}>
  <TreeView.Label>Files</TreeView.Label>
  <TreeView.Tree>
    <TreeView.NodeProvider node={collection.rootNode.children[0]} indexPath={[0]}>
      <TreeView.Branch>
        <TreeView.BranchControl>
          <TreeView.BranchTrigger>
            <TreeView.BranchIndicator />
          </TreeView.BranchTrigger>
          <TreeView.BranchText>src</TreeView.BranchText>
        </TreeView.BranchControl>
        <TreeView.BranchContent>
          <TreeView.NodeProvider node={collection.rootNode.children[0].children[0]} indexPath={[0, 0]}>
            <TreeView.Item>
              <TreeView.ItemText>index.tsx</TreeView.ItemText>
            </TreeView.Item>
          </TreeView.NodeProvider>
        </TreeView.BranchContent>
      </TreeView.Branch>
    </TreeView.NodeProvider>
  </TreeView.Tree>
</TreeView.Root>

For real-world usage, a recursive renderNode function is the recommended pattern:

function renderNode(node: FileNode, indexPath: number[]) {
  return (
    <TreeView.NodeProvider key={node.id} node={node} indexPath={indexPath}>
      {node.children ? (
        <TreeView.Branch>
          <TreeView.BranchControl>
            <TreeView.BranchTrigger>
              <TreeView.BranchIndicator />
            </TreeView.BranchTrigger>
            <TreeView.BranchText>{node.name}</TreeView.BranchText>
          </TreeView.BranchControl>
          <TreeView.BranchContent>
            {node.children.map((child, i) => renderNode(child, [...indexPath, i]))}
          </TreeView.BranchContent>
        </TreeView.Branch>
      ) : (
        <TreeView.Item>
          <TreeView.ItemText>{node.name}</TreeView.ItemText>
        </TreeView.Item>
      )}
    </TreeView.NodeProvider>
  )
}

Examples

Default

Loading preview...

Sizes

Three sizes are available: xs, sm, and md (default). The size controls text size, padding, and icon dimensions throughout the tree.

Loading preview...

Variants

Three visual variants control the hover and selection appearance: subtle (default), outline, and solid.

Loading preview...

Indent Guides

Add TreeView.BranchIndentGuide as the first child of TreeView.BranchContent to render a vertical line that visually connects parent nodes to their children.

Loading preview...

Checkboxes

Add TreeView.NodeCheckbox and TreeView.NodeCheckboxIndicator to each node to enable multi-select with checkboxes. Branch nodes automatically track the indeterminate state when their children are partially checked — pass the indeterminate character as the indeterminate prop.

Loading preview...

Disabled Nodes

Pass an isNodeDisabled callback to createTreeCollection to mark specific nodes as non-interactive. Disabled nodes are still visible but cannot be focused, selected, or expanded.

Loading preview...

Controlled

Control both the expanded and selected state externally by passing expandedValue and selectedValue alongside their change handlers. This is useful when you need to sync the tree state with other parts of your UI.

Loading preview...

Node Renaming

Enable inline renaming by passing canRename to TreeView.Root. Add TreeView.NodeRenameInput inside each node — it will appear automatically when the node enters rename mode. Press F2 to activate rename mode on the focused node. Press Enter to confirm or Escape to cancel.

Loading preview...

Expand and Collapse All

By using controlled expandedValue state, you can programmatically expand or collapse all branches at once by setting the value to an array of all branch IDs (expand) or an empty array (collapse).

Loading preview...

Keyboard Navigation

KeyDescription
TabBrings focus into the tree, targeting the first item.
Enter SpaceSelects the focused item or branch.
ArrowDownFocuses the next visible node.
ArrowUpFocuses the previous visible node.
ArrowRightOpens a collapsed branch. If the branch is already open, focuses its first child.
ArrowLeftCloses an open branch. If already closed, moves focus up to the parent branch.
HomeJumps focus to the very first node in the tree without affecting expand/collapse state.
EndJumps focus to the last reachable node, skipping past any collapsed branches.
a-z A-ZTypeahead — jumps to the next node whose label begins with the typed character. Closed branch descendants are excluded from the search.
*Expands all branches at the same depth as the currently focused node.
Shift + ArrowDownExtends the selection downward, toggling the next node's selected state.
Shift + ArrowUpExtends the selection upward, toggling the previous node's selected state.
Ctrl + AToggles selection of all nodes — selects everything if any are unselected, otherwise clears all.

API Reference

TreeView.Root

PropTypeDefaultDescription
collectionTreeCollection<T>requiredTree data built with createTreeCollection
size'xs' | 'sm' | 'md''md'Controls text size, padding, and icon sizes
variant'subtle' | 'outline' | 'solid''subtle'Visual style of hover and selection states
defaultExpandedValuestring[][]Node IDs expanded on initial render
expandedValuestring[]—Controlled expanded state
onExpandedChange(details: { expandedValue: string[] }) => void—Fired when any node expands or collapses
defaultSelectedValuestring[][]Node IDs selected on initial render
selectedValuestring[]—Controlled selection state
onSelectionChange(details: { selectedValue: string[] }) => void—Fired when selection changes
selectionMode'single' | 'multiple''single'Single or multi-node selection
defaultCheckedValuestring[][]Node IDs checked on initial render (checkbox mode)
checkedValuestring[]—Controlled checked state
onCheckedChange(details: { checkedValue: string[] }) => void—Fired when checked nodes change
canRename(node: T, indexPath: IndexPath) => boolean—Determines if a node supports inline renaming
onRenameComplete(details: RenameCompleteDetails) => void—Fired when a rename is committed
expandOnClickbooleantrueWhether clicking a branch label expands it
typeaheadbooleantrueEnables keyboard typeahead navigation
lazyMountbooleanfalseDefers rendering collapsed branch content until first expansion
unmountOnExitbooleanfalseRemoves collapsed branch content from the DOM

createTreeCollection

OptionTypeRequiredDescription
rootNodeTYesRoot of your data tree. Its children become the top-level nodes; the root itself is not rendered
nodeToValue(node: T) => stringYesExtracts a unique string ID for each node
nodeToString(node: T) => stringYesExtracts the display label for each node
nodeToChildren(node: T) => T[]NoExtracts the children array from a node. Omit for flat lists
isNodeDisabled(node: T) => booleanNoReturn true to mark a node as non-interactive

Sub-components

All sub-components accept the same props as their underlying Ark UI counterpart plus PandaCSS style props. The table below documents the rendered element and purpose of each.

Sub-componentRendersPurpose
TreeView.Label<h3>Accessible label for the tree
TreeView.Tree<div>Root list container
TreeView.Branch<div>A node that has children
TreeView.BranchControl<div>Clickable row for a branch node
TreeView.BranchTrigger<div>Clickable control that toggles expand/collapse
TreeView.BranchIndicator<div>Animated chevron/icon slot
TreeView.BranchText<span>Label text for a branch
TreeView.BranchContent<div>Container for child nodes
TreeView.BranchIndentGuide<div>Vertical indent line
TreeView.Item<div>A leaf node (no children)
TreeView.ItemText<span>Label text for a leaf node
TreeView.ItemIndicator<div>Icon/indicator slot for leaf nodes
TreeView.NodeCheckbox<span>Checkbox control for multi-select
TreeView.NodeCheckboxIndicator—Visual checkmark/indeterminate indicator
TreeView.NodeRenameInput<input>Inline rename input, shown in rename mode
TreeView.NodeProvider—Context provider — wraps each node to supply tree state
TreeView.NodeContext—Render-prop component — provides current node's state to children

Built with ❤️ by the carbonteq team. The source code is available on GitHub.

© 2026 Pallas UI. All rights reserved.

Project Files

src
components
Button.tsx
Input.tsx
index.tsx
public
favicon.ico
package.json

Project Files

src
components
Button.tsx
Input.tsx
index.tsx
public
favicon.ico
package.json

Size: xs

src
index.tsx
app.tsx
package.json

Size: sm

src
index.tsx
app.tsx
package.json

Size: md

src
index.tsx
app.tsx
package.json

Subtle

src
index.tsx
app.tsx
package.json

Outline

src
index.tsx
app.tsx
package.json

Solid

src
index.tsx
app.tsx
package.json

Project Files

src
components
Button.tsx
Input.tsx
index.tsx
public
favicon.ico
package.json

Select Files

src
components
Button.tsx
Input.tsx
index.tsx
public
favicon.ico

Project Files

src
Button.tsx
Input.tsx
README.md
package.json

Expanded

src

Selected

none

Project Files

src
components
Button.tsx
Input.tsx
index.tsx
public
favicon.ico
package.json

Project Files (double-click to rename)

src
Button.tsx
Input.tsx
README.md
package.json

Project Files

src
components
Button.tsx
Input.tsx
index.tsx
public
favicon.ico
package.json