Skip to content

VirtualTree

A tree view with expand/collapse, configurable indent per depth level, and optional lazy (async) child loading.

Props

nodes

TreeNode<T>[]

Root nodes.

indent

number · default: 20

Pixel indent per depth level.

estimatedItemSize

number · default: 36

Estimated row height.

overscan

number · default: 5

Extra rows outside the viewport.

onLoadChildren

(node) => Promise<TreeNode<T>[]>

Called when a node with hasChildren: true is first expanded.

motionBlur

boolean · default: false

Apply a CSS blur that scales with scroll velocity while scrolling fast.

Tree Node

TreeNode<T> — the shape of each entry in the nodes prop.

ts
interface TreeNode<T extends Record<string, unknown> = Record<string, unknown>> {
  id: string | number
  data: T
  children?: TreeNode<T>[]
  hasChildren?: boolean // true = has children that haven't been loaded yet
}

Row Data

FlatTreeRow<T> — the slot scope exposes:

ts
interface FlatTreeRow<T> {
  node: TreeNode<T>
  depth: number
  isExpanded: boolean
  hasChildren: boolean
  isLoading: boolean // true while onLoadChildren is running
}

Slots

default

Scope: { row: FlatTreeRow<T>, index: number }

Custom row content. The toggle button is rendered by the component.

empty

Scope: No scope

Empty state.

Emits

node-expand

Payload: TreeNode<T>

node-collapse

Payload: TreeNode<T>

node-click

Payload: [TreeNode<T>, depth: number]

Exposed API

expandAll()

Expand every node, including nested children already loaded.

collapseAll()

Collapse every node.

scrollTo(index, align?, options?)

Scroll to a flat row index. align is 'start' | 'center' | 'end' | 'auto'.

getScrollElement()

Returns the element that actually scrolls — pair with VirtualScrollbar.

expandedIds

Readonly<Ref<Set<string | number>>> — the currently expanded node IDs.

ts
treeRef.value?.expandAll()
treeRef.value?.collapseAll()
treeRef.value?.scrollTo(index, 'start')
treeRef.value?.scrollTo(index, 'start', { behavior: 'smooth' })
treeRef.value?.getScrollElement() // pair with VirtualScrollbar
treeRef.value?.expandedIds // Readonly<Ref<Set<string | number>>>

Example

vue
<script setup lang="ts">
import { VirtualTree } from 'vue-virtual-scroller-kit'
import type { TreeNode, FlatTreeRow } from 'vue-virtual-scroller-kit'

interface FileNode {
  name: string
  type: 'file' | 'folder'
}

const nodes: TreeNode<FileNode>[] = [
  {
    id: 1,
    data: { name: 'src', type: 'folder' },
    children: [
      { id: 2, data: { name: 'main.ts', type: 'file' } },
      { id: 3, data: { name: 'App.vue', type: 'file' } },
    ],
  },
  {
    id: 4,
    data: { name: 'node_modules', type: 'folder' },
    hasChildren: true, // lazy-loaded
  },
]

async function loadChildren(node: TreeNode<FileNode>): Promise<TreeNode<FileNode>[]> {
  const res = await fetch(`/api/children/${node.id}`)
  return res.json()
}
</script>

<template>
  <VirtualTree
    :nodes="nodes"
    :indent="20"
    :on-load-children="loadChildren"
    style="height: 400px"
    @node-click="(node, depth) => console.log(node.data.name, depth)"
  >
    <template #default="{ row }">
      <span>{{ row.node.data.type === 'folder' ? '📁' : '📄' }} {{ row.node.data.name }}</span>
    </template>
  </VirtualTree>
</template>