createNested
Hierarchical tree management built on createGroup, with parent-child relationships, open/close state, and pluggable traversal strategies.
Usage
The createNested composable manages hierarchical tree structures with parent-child relationships, open/close states, and tree traversal.
import { createNested } from '@vuetify/v0'
const tree = createNested({ open: 'multiple', selection: 'cascade' })
tree.onboard([
{
id: 'root',
value: 'Root',
children: [
{ id: 'child-1', value: 'Child 1' },
{ id: 'child-2', value: 'Child 2' },
],
},
])
tree.open('root')
tree.select('child-1')Architecture
createNested extends createGroup with hierarchical tree management:
Reactivity
createNested uses shallowReactive for tree state, making structural changes reactive while keeping traversal methods non-reactive for performance.
| Property/Method | Reactive | Notes |
|---|---|---|
children | ShallowReactive Map | |
parents | ShallowReactive Map | |
openedIds | ShallowReactive Set | |
openedItems | Computed from openedIds | |
rootIds | ShallowReactive Set — IDs of all top-level (parentless) nodes | |
roots | Computed, root nodes | |
leaves | Computed, leaf nodes | |
ticket.isOpen | Ref via toRef() | |
ticket.isLeaf | Ref via toRef() | |
ticket.depth | Ref via toRef() |
Examples
Options
open
Controls how nodes expand/collapse:
| Value | Behavior |
|---|---|
'multiple' | Multiple nodes can be open simultaneously (default) |
'single' | Only one node open at a time (accordion behavior) |
// Tree view - multiple nodes open
const tree = createNested({ open: 'multiple' })
// Accordion - single node open
const accordion = createNested({ open: 'single' })mandatory
When true, deselecting is prevented if it would leave no items selected:
const tree = createNested({ selection: 'cascade', mandatory: true })
tree.select('child-1')
tree.unselect('child-1') // no-op — would deselect the only selected itemunselectAll() with mandatory: true keeps the first selected item rather than clearing.
multiple
When false, selecting a node in cascade mode clears previous selections first (default: true):
const tree = createNested({ selection: 'cascade', multiple: false })
tree.select('child-1')
tree.select('child-2') // child-1 is deselected firstdisabled
When true, all tree mutations (open(), close(), select(), unselect(), toggle()) become no-ops. Individual tickets can also carry a disabled flag to skip only that node:
const tree = createNested({ disabled: true })
tree.open('branch-1') // no-op — tree is disabled
tree.select('leaf-1') // no-opAccepts MaybeRefOrGetter<boolean> for reactive toggling:
const isLocked = shallowRef(false)
const tree = createNested({ disabled: isLocked })selection
Controls how selection cascades through the hierarchy:
| Value | Behavior |
|---|---|
'cascade' | Selecting parent selects all descendants; ancestors show mixed state (default) |
'independent' | Each node selected independently, no cascading |
'leaf' | Only leaf nodes can be selected; parent selection selects leaf descendants |
// Cascading checkbox tree
const tree = createNested({ selection: 'cascade' })
// Independent selection
const flat = createNested({ selection: 'independent' })
// Leaf-only selection (file picker)
const picker = createNested({ selection: 'leaf' })Selection Modes
Cascade Mode (Default)
Selection propagates through the hierarchy:
Selecting a parent selects all descendants:
tree.select('root')
// root, child-1, child-2, grandchild-1, etc. are all selectedSelecting a child updates ancestors to mixed state:
tree.select('child-1')
// child-1 is selected
// root shows mixed state (some children selected)Automatic state resolution:
All children selected → Parent becomes selected (not mixed)
Some children selected → Parent becomes mixed
No children selected → Parent becomes unselected (not mixed)
Independent Mode
Each node is selected independently with no cascading:
const tree = createNested({ selection: 'independent' })
tree.select('parent')
// Only 'parent' is selected, children unchangedLeaf Mode
Only leaf nodes can be selected. Selecting a parent selects all leaf descendants:
const tree = createNested({ selection: 'leaf' })
tree.select('folder')
// All files (leaves) under 'folder' are selected
// 'folder' itself is not in selectedIdsConvenience Methods
Expand/Collapse All
// Open all non-leaf nodes
tree.expandAll()
// Close all nodes
tree.collapseAll()Data Transformation
Convert tree to flat array for serialization or API consumption:
const flat = tree.toFlat()
// Returns: [{ id, parentId, value }, ...]
// Useful for sending to APIs or AI systems
console.log(JSON.stringify(flat))Inline Children Registration
Define children directly when registering items:
tree.onboard([
{
id: 'nav',
value: 'Navigation',
children: [
{ id: 'home', value: 'Home' },
{ id: 'about', value: 'About' },
{
id: 'products',
value: 'Products',
children: [
{ id: 'widgets', value: 'Widgets' },
{ id: 'gadgets', value: 'Gadgets' },
],
},
],
},
])Cascade Unregister
Remove a node and optionally all its descendants:
// Remove node, orphan children (default)
tree.unregister('parent')
// Remove node and all descendants
tree.unregister('parent', true)
// Batch removal with cascade
tree.offboard(['node-1', 'node-2'], true)Ticket Properties
Each registered node receives additional properties:
const node = tree.register({ id: 'node', value: 'Node', parentId: 'root' })
// Reactive refs
node.isOpen.value // boolean - is this node open?
node.isLeaf.value // boolean - has no children?
node.depth.value // number - depth in tree (0 = root)
// Methods
node.open() // Open this node
node.close() // Close this node
node.flip() // Flip open/closed state
node.getPath() // Get path from root to this node
node.getAncestors() // Get all ancestors
node.getDescendants() // Get all descendantsContext Pattern
Use with Vue’s provide/inject for component trees:
import { createNestedContext } from '@vuetify/v0'
// Create a trinity
const [useTree, provideTree, defaultTree] = createNestedContext({
namespace: 'my-tree',
})
// In parent component
provideTree()
// In child components
const tree = useTree()Benchmarks
Every operation is profiled across multiple dataset sizes to measure real-world throughput. Each benchmark is assigned a performance tier—good, fast, blazing, or slow—and groups are scored by averaging their individual results so you can spot bottlenecks at a glance. This transparency helps you make informed decisions about which patterns scale for your use case. Learn more in the benchmarks guide.