Skip to main content
Vuetify0 is now a release candidate!
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

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.

vue
<script setup lang="ts">
  import { createVirtual } from '@vuetify/v0'
  import { shallowRef } from 'vue'

  const items = shallowRef(
    Array.from({ length: 10_000 }, (_, i) => `Item ${i + 1}`)
  )

  const { element, items: visible, offset, size, scroll, scrollTo } = createVirtual(items, {
    itemHeight: 40,
  })
</script>

<template>
  <div ref="element" class="h-[300px] overflow-y-auto" @scroll="scroll">
    <div :style="{ height: `${offset}px` }" />
    <div v-for="item in visible" :key="item.index">
      {{ item.raw }}
    </div>
    <div :style="{ height: `${size}px` }" />
  </div>
</template>

Context / DI

Use createVirtualContext to share a virtual scroll instance across a component tree:

ts
import { createVirtualContext } from '@vuetify/v0'
import { shallowRef } from 'vue'

const items = shallowRef([...])

export const [useVirtual, provideVirtual, virtual] =
  createVirtualContext(items, {
    namespace: 'my:virtual',
    itemHeight: 40,
  })

// In parent component
provideVirtual()

// In child component
const { items: visible, offset, size, scroll } = useVirtual()

Architecture

The rendering pipeline transforms scroll events into visible item ranges:

Virtual Rendering Pipeline

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

Virtual Rendering Pipeline

Reactivity

Property/MethodReactiveNotes
elementRef, assign scroll container
itemsComputed, visible items with index
offsetShallowRef, readonly (top spacer height)
sizeShallowRef, readonly (bottom spacer height)
stateShallowRef ('loading' | 'empty' | 'error' | 'ok')
scroll()Call on @scroll; schedules a visible-range update via rAF
scrollend()Call on @scrollend; alias of scroll()
scrollTo(index, options?)Scroll to item by index; accepts behavior, block, offset
resize(index, height)Notify of a dynamic item height change; triggers offset rebuild
reset()Reset state to 'ok' and restore scroll anchor
Tip

Source items The items ref passed to createVirtual() is watched for changes. When items change, the virtual scroller updates automatically.

Examples

Virtualized Employee Directory

A 10,000-row employee directory that mounts only the rows visible inside its scroll container — typically under twenty nodes — no matter how large the source array grows. The toolbar reads out how many rows are rendered versus the total, so you can watch the render count stay in the low double digits while the dataset balloons.

createVirtual(rows, { itemHeight: 44 }) returns element, items (the visible slice, each { raw, index }), offset (top-spacer height), size (bottom-spacer height), scroll, and scrollTo. The view component binds ref="element" to the scroll container, calls @scroll="scroll" to schedule a visible-range recalculation, and sandwiches the rendered rows between two spacer elements sized by offset and size — that pair of spacers is what keeps the native scrollbar proportional to the full list without ever mounting every row. The jump control is a NumberField whose value drives scrollTo(index, { behavior: 'smooth' }), with the index bounded by the v0 clamp utility so it can never point past the data.

The example is split so each layer is reusable in isolation. useDirectory.ts is a DOM-free data source that owns the row array and a grow operation; VirtualDirectory.vue accepts any rows array and applies the windowing — createVirtual watches the reactive prop, so appending rows reflows the scroller automatically. Reach for createVirtual whenever rendering the whole list would cause layout thrash or memory pressure (rule of thumb: 500+ fixed-height rows, fewer when each row is complex). For variable-height rows call resize(index, height) after each row measures itself; to filter or sort before virtualizing, pair it with createFilter or createDataTable.

FileRole
useDirectory.tsDOM-free data source: owns the 10,000-row array and an append operation
VirtualDirectory.vueApplies createVirtual to the rows prop; renders the windowed list, jump control, and rendered-vs-total stat
directory.vueEntry: wires the data source to the view and adds the grow-dataset chrome

A 10,000-row employee directory — only the visible window is mounted to the DOM.

Rendering 0 of 10,000 rows

FAQ

Discord
Need help? Join our community for support and discussions ↗

API Reference

The following API details are for the createVirtual composable.

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?

Ctrl+/