Ark UI Logo
Collections
Tree collection

Tree Collection

Working with tree collections in Ark UI

A tree collection is designed to manage hierarchical data structures like file systems, navigation menus, or organization charts. It provides powerful methods for traversing, manipulating, and querying tree structures.

import { createTreeCollection } from '@ark-ui/react/collection'

const treeData = {
  value: 'root',
  label: 'Root',
  children: [
    {
      value: 'folder1',
      label: 'Folder 1',
      children: [
        { value: 'file1', label: 'File 1.txt' },
        { value: 'file2', label: 'File 2.txt' },
      ],
    },
    {
      value: 'folder2',
      label: 'Folder 2',
      children: [
        {
          value: 'subfolder1',
          label: 'Subfolder 1',
          children: [{ value: 'file3', label: 'File 3.txt' }],
        },
      ],
    },
  ],
}

const tree = createTreeCollection({ rootNode: treeData })

The tree collection provides various methods to navigate through the hierarchical structure.

Getting First and Last Nodes

const firstNode = tree.getFirstNode()
console.log(firstNode?.value) // "folder1"

const lastNode = tree.getLastNode()
console.log(lastNode?.value) // "folder2"

Sequential Navigation

Navigate to the next or previous node in the tree traversal order:

const nextNode = tree.getNextNode('file1')
console.log(nextNode?.value) // "file2"

const previousNode = tree.getPreviousNode('file2')
console.log(previousNode?.value) // "file1"

Hierarchical Relationships

Parent and Children

Get parent and descendant nodes:

// Get parent node
const parentNode = tree.getParentNode('file1')
console.log(parentNode?.value) // "folder1"

// Get all ancestor nodes
const ancestors = tree.getParentNodes('file3')
console.log(ancestors.map((n) => n.value)) // ["folder2", "subfolder1"]

// Get all descendant nodes
const descendants = tree.getDescendantNodes('folder1')
console.log(descendants.map((n) => n.value)) // ["file1", "file2"]

// Get descendant values only
const descendantValues = tree.getDescendantValues('folder2')
console.log(descendantValues) // ["subfolder1", "file3"]

Sibling Navigation

Navigate between sibling nodes:

// Assuming we have the index path of "file1"
const indexPath = tree.getIndexPath('file1') // [0, 0]

const nextSibling = tree.getNextSibling(indexPath)
console.log(nextSibling?.value) // "file2"

const previousSibling = tree.getPreviousSibling(indexPath)
console.log(previousSibling) // undefined (no previous sibling)

// Get all siblings
const siblings = tree.getSiblingNodes(indexPath)
console.log(siblings.map((n) => n.value)) // ["file1", "file2"]

Index Path Operations

Work with index paths to identify node positions:

// Get index path for a value
const indexPath = tree.getIndexPath('file3')
console.log(indexPath) // [1, 0, 0]

// Get value from index path
const value = tree.getValue([1, 0, 0])
console.log(value) // "file3"

// Get full value path (all ancestors + node)
const valuePath = tree.getValuePath([1, 0, 0])
console.log(valuePath) // ["folder2", "subfolder1", "file3"]

// Get node at specific index path
const node = tree.at([1, 0])
console.log(node?.value) // "subfolder1"

Tree Queries

Branch and Leaf Detection

// Check if a node is a branch (has children)
const folder1Node = tree.findNode('folder1')
const isBranch = tree.isBranchNode(folder1Node!)
console.log(isBranch) // true

// Get all branch values
const branchValues = tree.getBranchValues()
console.log(branchValues) // ["folder1", "folder2", "subfolder1"]

Tree Traversal

Visit all nodes with custom logic:

tree.visit({
  onEnter: (node, indexPath) => {
    console.log(`Visiting: ${node.value} at depth ${indexPath.length}`)

    // Skip certain branches
    if (node.value === 'folder2') {
      return 'skip' // Skip this branch and its children
    }
  },
})

Filtering

Create a new tree with filtered nodes:

// Keep only nodes that match criteria
const filteredTree = tree.filter((node, indexPath) => {
  return node.value.includes('file') // Only keep files
})

console.log(filteredTree.getValues()) // ["file1", "file2", "file3"]

Tree Manipulation

Adding Nodes

const newFile = { value: 'newfile', label: 'New File.txt' }

// Insert after a specific node
const indexPath = tree.getIndexPath('file1')
const updatedTree = tree.insertAfter(indexPath!, [newFile])

// Insert before a specific node
const updatedTree2 = tree.insertBefore(indexPath!, [newFile])

Removing Nodes

const indexPath = tree.getIndexPath('file2')
const updatedTree = tree.remove([indexPath!])

console.log(updatedTree.getValues()) // file2 is removed

Moving Nodes

const fromIndexPaths = [tree.getIndexPath('file1')!]
const toIndexPath = tree.getIndexPath('folder2')!

const updatedTree = tree.move(fromIndexPaths, toIndexPath)
// file1 is now moved under folder2

Replacing Nodes

const indexPath = tree.getIndexPath('file1')!
const newNode = { value: 'replacedfile', label: 'Replaced File.txt' }

const updatedTree = tree.replace(indexPath, newNode)

Utility Methods

Flattening

Convert the tree to a flat structure:

const flatNodes = tree.flatten()
console.log(flatNodes.map((n) => ({ value: n.value, depth: n._indexPath.length })))
// [{ value: "folder1", depth: 1 }, { value: "file1", depth: 2 }, ...]

Getting All Values

const allValues = tree.getValues()
console.log(allValues) // ["folder1", "file1", "file2", "folder2", "subfolder1", "file3"]

Depth Calculation

const depth = tree.getDepth('file3')
console.log(depth) // 3 (root -> folder2 -> subfolder1 -> file3)

Working with Custom Node Types

You can customize how the tree collection interprets your data:

interface CustomNode {
  id: string
  name: string
  items?: CustomNode[]
  isDisabled?: boolean
}

const customTree = createTreeCollection<CustomNode>({
  rootNode: {
    id: 'root',
    name: 'Root',
    items: [
      { id: '1', name: 'Item 1', isDisabled: false },
      { id: '2', name: 'Item 2', isDisabled: true },
    ],
  },
  nodeToValue: (node) => node.id,
  nodeToString: (node) => node.name,
  nodeToChildren: (node) => node.items,
  isNodeDisabled: (node) => node.isDisabled ?? false,
})

Creating Trees from File Paths

Create a tree structure from file paths:

import { createFileTreeCollection } from '@ark-ui/react/collection'

const paths = ['src/components/Button.tsx', 'src/components/Input.tsx', 'src/utils/helpers.ts', 'docs/README.md']

const fileTree = createFileTreeCollection(paths)
console.log(fileTree.getBranchValues()) // ["src", "components", "utils", "docs"]

Good to know: Tree collections are immutable - all modification methods return a new tree instance rather than modifying the original.