Skip to main content
You are viewing Pre-Alpha documentation.
Vuetify0 Logo
Theme
Mode
Accessibility
Vuetify
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

The createNested composable extends createGroup to manage hierarchical tree structures. It provides parent-child relationship tracking, open/close state management, tree traversal utilities, and pluggable open strategies.

Usage

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

0 selected / 1 open / 16 total
<script setup lang="ts">
  import { createNested } from '@vuetify/v0'
  import { computed } from 'vue'

  const nav = createNested()

  // Register navigation with inline children
  nav.onboard([
    {
      id: 'getting-started',
      value: 'Getting Started',
      children: [
        { id: 'installation', value: 'Installation' },
        { id: 'quick-start', value: 'Quick Start' },
        { id: 'typescript', value: 'TypeScript Support' },
      ],
    },
    {
      id: 'components',
      value: 'Components',
      children: [
        { id: 'buttons', value: 'Buttons' },
        {
          id: 'inputs',
          value: 'Inputs',
          children: [
            { id: 'text-field', value: 'Text Field' },
            { id: 'checkbox', value: 'Checkbox' },
            { id: 'radio', value: 'Radio' },
          ],
        },
        { id: 'selection', value: 'Selection' },
      ],
    },
    {
      id: 'composables',
      value: 'Composables',
      children: [
        { id: 'use-theme', value: 'useTheme' },
        { id: 'use-form', value: 'useForm' },
        { id: 'use-nested', value: 'useNested' },
      ],
    },
    { id: 'changelog', value: 'Changelog' },
  ])

  nav.open('getting-started')

  const stats = computed(() => ({
    total: nav.size,
    opened: nav.openedIds.size,
    selected: nav.selectedIds.size,
  }))

  function getVisibleNodes (parentId?: string): string[] {
    const ids = parentId
      ? nav.children.get(parentId) ?? []
      : nav.roots.value.map(r => r.id)

    return ids.flatMap(id => {
      const result = [id]
      if (nav.opened(id)) {
        result.push(...getVisibleNodes(id))
      }
      return result
    })
  }

  const visibleNodes = computed(() => getVisibleNodes())

  function onCheckboxClick (e: Event, id: string) {
    e.stopPropagation()
    nav.toggle(id)
  }
</script>

<template>
  <div class="flex flex-col gap-3">
    <div class="flex gap-2 flex-wrap">
      <button
        class="px-3 py-1 border border-divider rounded hover:bg-surface-tint text-sm"
        @click="nav.expandAll()"
      >
        Expand All
      </button>

      <button
        class="px-3 py-1 border border-divider rounded hover:bg-surface-tint text-sm"
        @click="nav.collapseAll()"
      >
        Collapse All
      </button>

      <span class="text-on-surface opacity-60 text-sm ml-auto">
        {{ stats.selected }} selected / {{ stats.opened }} open / {{ stats.total }} total
      </span>
    </div>

    <nav class="border border-divider rounded overflow-hidden">
      <div
        v-for="id in visibleNodes"
        :key="id"
        class="flex items-center gap-2 py-2 hover:bg-surface-tint border-b border-divider last:border-b-0"
        :class="{ 'cursor-pointer': !nav.isLeaf(id) }"
        :style="{ paddingLeft: `${nav.getDepth(id) * 16 + 12}px` }"
        @click="nav.flip(id)"
      >
        <span
          v-if="!nav.isLeaf(id)"
          class="w-4 text-center opacity-60"
        >
          {{ nav.opened(id) ? '-' : '+' }}
        </span>
        <span v-else class="w-4" />

        <button
          class="size-4 border rounded inline-flex items-center justify-center border-divider text-xs"
          :class="nav.selected(id) ? 'bg-primary border-primary text-on-primary' : nav.mixed(id) ? 'bg-primary border-primary text-on-primary' : ''"
          @click="onCheckboxClick($event, id)"
        >
          <span v-if="nav.selected(id)">✓</span>
          <span v-else-if="nav.mixed(id)">-</span>
        </button>

        <span
          class="text-sm"
          :class="nav.isLeaf(id) ? 'opacity-80' : 'font-medium'"
        >
          {{ nav.get(id)?.value }}
        </span>
      </div>
    </nav>
  </div>
</template>

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
rootsComputed, root nodes
leavesComputed, leaf nodes
ticket.isOpenRef via toRef()
ticket.isLeafRef via toRef()
ticket.depthRef via toRef()

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

API Reference

The following API details are for the createNested composable.

Functions

createNested

(_options?: NestedOptions) => R

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<R>

Creates a new nested context with provide/inject pattern.

useNested

(namespace?: string) => R

Returns the current nested instance from context.

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' })

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

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+/