Skip to main content
Vuetify0 is now in alpha!
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

createNested

Hierarchical tree management built on createGroup, with parent-child relationships, open/close state, and pluggable traversal strategies.


AdvancedApr 5, 2026

Usage

The createNested composable manages hierarchical tree structures with parent-child relationships, open/close states, and tree traversal.

ts
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:

Nested Hierarchy

Use controls to zoom and pan. Click outside or press Escape to close.

Nested Hierarchy

Reactivity

createNested uses shallowReactive for tree state, making structural changes reactive while keeping traversal methods non-reactive for performance.

Property/MethodReactiveNotes
childrenShallowReactive Map
parentsShallowReactive Map
openedIdsShallowReactive Set
openedItemsComputed from openedIds
rootIdsShallowReactive Set — IDs of all top-level (parentless) nodes
rootsComputed, root nodes
leavesComputed, leaf nodes
ticket.isOpenRef via toRef()
ticket.isLeafRef via toRef()
ticket.depthRef via toRef()

Examples

Collapsible Navigation Tree

A nested tree with expand/collapse all, multi-select checkboxes, and visual parent/child relationship indicators.

0 selected / 1 open / 16 total

Options

open

Controls how nodes expand/collapse:

ValueBehavior
'multiple'Multiple nodes can be open simultaneously (default)
'single'Only one node open at a time (accordion behavior)
ts
// 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:

ts
const tree = createNested({ selection: 'cascade', mandatory: true })

tree.select('child-1')
tree.unselect('child-1') // no-op — would deselect the only selected item

unselectAll() 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):

ts
const tree = createNested({ selection: 'cascade', multiple: false })

tree.select('child-1')
tree.select('child-2') // child-1 is deselected first

disabled

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:

ts
const tree = createNested({ disabled: true })

tree.open('branch-1')   // no-op — tree is disabled
tree.select('leaf-1')   // no-op

Accepts MaybeRefOrGetter<boolean> for reactive toggling:

ts
const isLocked = shallowRef(false)
const tree = createNested({ disabled: isLocked })

selection

Controls how selection cascades through the hierarchy:

ValueBehavior
'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
ts
// 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:

ts
tree.select('root')
// root, child-1, child-2, grandchild-1, etc. are all selected

Selecting a child updates ancestors to mixed state:

ts
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:

ts
const tree = createNested({ selection: 'independent' })

tree.select('parent')
// Only 'parent' is selected, children unchanged

Leaf Mode

Only leaf nodes can be selected. Selecting a parent selects all leaf descendants:

ts
const tree = createNested({ selection: 'leaf' })

tree.select('folder')
// All files (leaves) under 'folder' are selected
// 'folder' itself is not in selectedIds

Custom Open Strategies

For advanced use cases, implement custom strategies:

ts
import type { OpenStrategy, OpenStrategyContext } from '@vuetify/v0'

const keepParentsOpenStrategy: OpenStrategy = {
  onOpen: (id, context) => {
    // context.openedIds - reactive Set of open node IDs
    // context.children - Map of parent ID to child IDs
    // context.parents - Map of child ID to parent ID
  },
  onClose: (id, context) => {
    // Called after a node is closed
  },
}

const tree = createNested({ openStrategy: keepParentsOpenStrategy })
Tip

The openStrategy option overrides open when provided. Use open for simple cases.

Convenience Methods

Expand/Collapse All

ts
// Open all non-leaf nodes
tree.expandAll()

// Close all nodes
tree.collapseAll()

Data Transformation

Convert tree to flat array for serialization or API consumption:

ts
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:

ts
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:

ts
// 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:

ts
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 descendants

Context Pattern

Use with Vue’s provide/inject for component trees:

ts
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()

API Reference

The following API details are for the createNested composable.

Functions

createNested

(_options?: NestedOptions) => NestedContext<NestedTicketInput<unknown>, NestedTicket<NestedTicketInput<unknown>>>

Creates a new nested tree instance with hierarchical management. Extends `createGroup` to support parent-child relationships, tree traversal, and open/close state management. Perfect for tree views, nested navigation, and hierarchical data structures.

createNestedContext

(_options?: NestedContextOptions) => ContextTrinity<NestedContext<NestedTicketInput<unknown>, NestedTicket<NestedTicketInput<unknown>>>>

Creates a new nested context with provide/inject pattern.

useNested

(namespace?: string) => NestedContext<NestedTicketInput<unknown>, NestedTicket<NestedTicketInput<unknown>>>

Returns the current nested instance from context.

Options

events

boolean | undefined

Enable event emission for registry operations

Default: false

reactive

boolean | undefined

Enable reactive behavior for registry operations

Default: false

disabled

MaybeRefOrGetter<boolean> | undefined

Disabled state for the entire model instance

Default: false

enroll

MaybeRefOrGetter<boolean> | undefined

Auto-select tickets on registration

Default: true

multiple

MaybeRefOrGetter<boolean> | undefined

Allow multiple tickets to be selected simultaneously

Default: false

mandatory

MaybeRefOrGetter<boolean | "force"> | undefined

Controls mandatory selection behavior: - `false` (default): No mandatory selection enforcement - `true`: Prevents deselecting the last selected item - `'force'`: Automatically selects the first non-disabled item on registration

open

NestedOpenMode | undefined

Controls how nodes expand/collapse. - `'multiple'` (default): Multiple nodes can be open simultaneously - `'single'`: Only one node open at a time (accordion behavior)

openAll

boolean | undefined

When true, parent nodes automatically open when children are registered. Similar to `enroll` in selection composables but for open state.

reveal

boolean | undefined

When true, opening a node also opens all its ancestors. Ensures the opened node is always visible in the tree.

selection

NestedSelectionMode | undefined

Controls how selection cascades through the hierarchy. - `'cascade'` (default): Selecting parent selects descendants; ancestors show mixed state - `'independent'`: Each node selected independently - `'leaf'`: Only leaf nodes selectable; parent selection selects leaf descendants

active

NestedActiveMode | undefined

Controls how many items can be active/highlighted simultaneously. - `'single'` (default): Only one item active at a time - `'multiple'`: Multiple items can be active simultaneously

openStrategy

OpenStrategy | undefined

Advanced: Custom strategy for open behavior. Overrides `open` option if provided.

Properties

collection

ReadonlyMap<ID, E>

The collection of tickets in the registry

size

number

The number of tickets in the registry

selectedIds

Reactive<Set<ID>>

Set of currently selected ticket IDs

selectedItems

ComputedRef<Set<E>>

Computed Set of selected ticket instances

selectedValues

ComputedRef<Set<E["value"] extends Ref<infer U, infer U> ? U : E["value"]>>

Computed Set of selected ticket values

disabled

MaybeRefOrGetter<boolean>

Disabled state for the entire model instance

multiple

MaybeRefOrGetter<boolean>

Whether the selection allows multiple selections

selectedIndexes

ComputedRef<Set<number>>

mixedIds

Reactive<Set<ID>>

Set of mixed/indeterminate ticket IDs

mixedItems

ComputedRef<Set<E>>

Set of mixed/indeterminate ticket instances

isNoneSelected

ComputedRef<boolean>

Whether no items are currently selected

isAllSelected

ComputedRef<boolean>

Whether all selectable (non-disabled) items are selected

isMixed

ComputedRef<boolean>

Whether some but not all selectable items are selected

children

ReadonlyMap<ID, readonly ID[]>

Map of parent IDs to arrays of child IDs. Use register/unregister to modify.

parents

ReadonlyMap<ID, ID | undefined>

Map of child IDs to their parent ID (or undefined for roots). Use register/unregister to modify.

openedIds

Reactive<Set<ID>>

Reactive Set of opened/expanded item IDs. Use open/close/flip to modify.

openedItems

ComputedRef<Set<E>>

Computed Set of opened/expanded item instances

activeIds

Reactive<Set<ID>>

Reactive Set of active/highlighted item IDs. Use activate/deactivate to modify.

activeItems

ComputedRef<Set<E>>

Computed Set of active/highlighted item instances

activeIndexes

ComputedRef<Set<number>>

Computed Set of active item indexes (position-based)

roots

ComputedRef<E[]>

Computed array of root items (items with no parent)

leaves

ComputedRef<E[]>

Computed array of leaf items (items with no children)

openStrategy

OpenStrategy

Strategy controlling how items are opened

Methods

clear

() => void

Clear the entire registry

has

(id: ID) => boolean

Check if a ticket exists by ID

keys

() => readonly ID[]

Get all registered IDs

browse

(value: E["value"]) => ID[] | undefined

Browse for an ID(s) by value

lookup

(index: number) => ID | undefined

lookup a ticket by index number

get

(id: ID) => E | undefined

Get a ticket by ID

upsert

(id: ID, ticket?: Partial<Z>, event?: string) => E

Update or insert a ticket by ID

values

() => readonly E[]

Get all values of registered tickets

entries

() => readonly [ID, E][]

Get all entries of registered tickets

unregister

(id: ID) => void

Unregister a ticket by ID

reindex

() => void

Reset the index directory and update all tickets

move

(id: ID, toIndex: number) => E | undefined

Seek for a ticket based on direction and optional predicate

seek

(direction?: "first" | "last", from?: number, predicate?: (ticket) => boolean) => E | undefined

on

<K extends Extensible<RegistryEventName>>(event: K, cb: EventHandler<E, K>) => void

Listen for registry events

off

<K extends Extensible<RegistryEventName>>(event: K, cb: EventHandler<E, K>) => void

Stop listening for registry events

emit

<K extends Extensible<RegistryEventName>>(event: K, data: EventPayload<E, K>) => void

Emit an event with data

dispose

() => void

Clears the registry and removes all listeners

offboard

(ids: ID[]) => void

Offboard multiple tickets at once

batch

<R>(fn: () => R) => R

Execute operations in a batch, deferring cache invalidation and event emission until complete

reset

() => void

Reset selection state without destroying the registry

selected

(id: ID) => boolean

Check if a ticket is currently selected

apply

(values: unknown[], options?: { multiple?) => void

Apply external values to the model

mandate

() => void

Mandate selected ID based on "mandatory" option

mix

(ids: ID | ID[]) => void

Set one or more Tickets to mixed/indeterminate state by ID

unmix

(ids: ID | ID[]) => void

Clear mixed/indeterminate state from one or more Tickets by ID

mixed

(id: ID) => boolean

Check if a ticket is in mixed/indeterminate state by ID

selectAll

() => void

Select all selectable (non-disabled) items

unselectAll

() => void

Unselect all items (respects mandatory option)

toggleAll

() => void

Toggle between all selected and none selected

activate

(ids: ID | ID[]) => void

Activate/highlight one or more items by ID

deactivate

(ids: ID | ID[]) => void

Deactivate/unhighlight one or more items by ID

activated

(id: ID) => boolean

Check if an item is active by ID

deactivateAll

() => void

Deactivate all items

open

(ids: ID | ID[]) => void

Open/expand one or more items by ID

close

(ids: ID | ID[]) => void

Close/collapse one or more items by ID

flip

(ids: ID | ID[]) => void

Flip one or more items' open/closed state by ID

opened

(id: ID) => boolean

Check if an item is open by ID

unfold

(ids: ID | ID[]) => void

Open node(s) and their immediate non-leaf children

reveal

(ids: ID | ID[]) => void

Reveal node(s) by opening all ancestors (makes node visible without opening it)

expand

(ids: ID | ID[]) => void

Fully expand node(s) and all their non-leaf descendants

expandAll

() => void

Expand all non-leaf nodes

collapseAll

() => void

Collapse all nodes

toFlat

() => Array<{ id: ID; parentId: ID | undefined; value: Z extends NestedTicketInput<infer V> ? V : unknown; }>

Convert tree to flat array with parentId references

getPath

(id: ID) => ID[]

Get the path from root to the specified item (inclusive)

getDescendants

(id: ID) => ID[]

Get all descendants of an item

getAncestors

(id: ID) => ID[]

Get all ancestors of an item (excluding self)

isLeaf

(id: ID) => boolean

Check if an item is a leaf node

getDepth

(id: ID) => number

Get the depth level of an item in the tree

isAncestorOf

(ancestorId: ID, descendantId: ID) => boolean

Check if ancestorId is an ancestor of descendantId

hasAncestor

(id: ID, ancestorId: ID) => boolean

Check if id has ancestorId as an ancestor (semantic alias for isAncestorOf)

siblings

(id: ID) => ID[]

Get sibling IDs (including self). For roots, returns all root IDs.

position

(id: ID) => number

Get 1-indexed position among siblings (for aria-posinset). Returns 0 if not found.

visibleItems

() => E[]

Returns depth-first traversal of visible (open) nodes for keyboard navigation

select

(ids: ID | ID[]) => void

Select item(s) and all descendants, updating ancestor mixed states

unselect

(ids: ID | ID[]) => void

Unselect item(s) and all descendants, updating ancestor mixed states

toggle

(ids: ID | ID[]) => void

Toggle selection with cascading behavior

register

(registration?: NestedRegistration<Z>) => E

Register a node with optional inline children (accepts input type, returns output type)

onboard

(registrations: NestedRegistration<Z>[]) => E[]

Batch register nodes with optional inline children

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.

View benchmark source↗

Was this page helpful?

© 2016-1970 Vuetify, LLC
Ctrl+/