createVirtual
Virtual scrolling composable for efficiently rendering large lists by only rendering visible items.
Usage
The createVirtual composable efficiently renders large lists by only mounting visible items plus a small overscan buffer. Pass an array of items and configure the item height to get back sliced items, scroll handlers, and positioning values.
<script setup lang="ts">
import { createVirtual } from '@vuetify/v0'
import { computed, shallowRef } from 'vue'
const items = shallowRef(
Array.from({ length: 10_000 }, (_, i) => ({
id: i,
name: `Item ${i + 1}`,
value: Math.floor(Math.random() * 1000),
})),
)
const virtual = createVirtual(items, { itemHeight: 40 })
const {
element,
items: virtualItems,
offset,
size,
scroll,
scrollTo,
} = virtual
const stats = computed(() => ({
total: items.value.length,
rendered: virtualItems.value.length,
}))
const jumpTo = shallowRef('')
function handleJumpTo () {
const index = Number.parseInt(jumpTo.value) - 1
if (index < 0 || index > items.value.length) return
scrollTo(index)
}
function addItems () {
const newItems = Array.from({ length: 100 }, (_, i) => ({
id: items.value.length + i,
name: `Item ${items.value.length + i + 1}`,
value: Math.floor(Math.random() * 1000),
}))
items.value = [...items.value, ...newItems]
}
</script>
<template>
<div class="flex flex-col gap-3">
<div class="flex gap-2 items-center text-sm flex-wrap">
<input
v-model="jumpTo"
class="px-2 py-1 border border-divider bg-surface text-on-surface rounded w-24 flex-1 md:flex-none"
placeholder="Jump to..."
type="number"
@keyup.enter="handleJumpTo"
>
<button class="px-3 py-1 border border-divider rounded hover:bg-surface-tint" @click="handleJumpTo">
Jump
</button>
<button class="px-3 py-1 border border-divider rounded hover:bg-surface-tint" @click="addItems">
Add 100
</button>
<span class="text-on-surface opacity-60 ml-auto">
{{ stats.rendered }} (rendered) / {{ stats.total }}
</span>
</div>
<div
ref="element"
class="h-[300px] overflow-y-auto border border-divider rounded"
@scroll="scroll"
>
<div :style="{ height: `${offset}px` }" />
<div
v-for="item in virtualItems"
:key="item.index"
class="h-[40px] px-4 flex items-center justify-between border-b border-divider hover:bg-surface-tint"
>
<span class="font-mono text-sm text-on-surface">{{ item.raw.name }}</span>
<span class="text-on-surface opacity-60">{{ item.raw.value }}</span>
</div>
<div :style="{ height: `${size}px` }" />
</div>
</div>
</template>
Architecture
The rendering pipeline transforms scroll events into visible item ranges:
Reactivity
| Property/Method | Reactive | Notes |
|---|---|---|
element | Ref, assign scroll container | |
items | Computed, visible items with index | |
offset | ShallowRef, readonly (top spacer height) | |
size | ShallowRef, readonly (bottom spacer height) | |
state | ShallowRef ('loading' | 'empty' | 'error' | 'ok') |
Source items The items ref passed to createVirtual() is watched for changes. When items change, the virtual scroller updates automatically.
Functions
createVirtual
(items: Ref<readonly T[], readonly T[]>, _options?: VirtualOptions) => VirtualContext<T>Virtual scrolling composable for efficiently rendering large lists
createVirtualContext
(items: Ref<readonly T[], readonly T[]>, _options?: VirtualContextOptions) => ContextTrinity<VirtualContext<T>>Creates a virtual scrolling context with dependency injection support.
useVirtual
(namespace?: string) => VirtualContext<T>Returns the current virtual context from dependency injection.
Options
onStartReached
(distance: number) => void | Promise<void>The callback to call when the start is reached.
Properties
Methods
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.