Components / Data

VirtualList

Render long lists efficiently with fixed-height rows and native scrolling.

Preview

Deployments

1,000 records, only visible rows rendered

Live
Deployment 0001Review
Deployment 0002Healthy
Deployment 0003Healthy
Deployment 0004Running
Deployment 0005Healthy
Deployment 0006Healthy
Deployment 0007Running

Use cases

Large activity log

Render only visible rows from thousands of fixed-height events.

Searchable directory

Virtualize a filtered flat collection while keeping stable item keys.

Usage example

Vue
<VirtualList :items="rows" :item-size="40" :get-key="row => row.id" class="h-64" role="list" aria-label="Deployments">
  <template #default="{ item }">
    <div role="listitem">{{ item.label }}</div>
  </template>
</VirtualList>

More examples

Deployment log

A fixed row height and stable ID let the list calculate its window.

Deployment log
<script setup lang="ts">
import VirtualList from './components/ui/virtual-list/VirtualList.vue'
const rows = Array.from({ length: 10000 }, (_, id) => ({ id, label: 'Deployment ' + (id + 1) }))
</script>

<template>
  <VirtualList :items="rows" :item-size="40" :get-key="row => row.id" class="h-72" role="list" aria-label="Deployments">
    <template #default="{ item }"><div role="listitem" class="h-10">{{ item.label }}</div></template>
  </VirtualList>
</template>

Filtered directory

Filter the source collection before passing it to the virtual window.

Filtered directory
<script setup lang="ts">
import { computed, ref } from 'vue'
import VirtualList from './components/ui/virtual-list/VirtualList.vue'
const query = ref('')
const people = Array.from({ length: 2000 }, (_, id) => ({ id, name: 'Member ' + id }))
const visible = computed(() => people.filter(person => person.name.toLowerCase().includes(query.value.toLowerCase())))
</script>

<template>
  <label>Find member <input v-model="query" /></label>
  <VirtualList :items="visible" :item-size="48" :get-key="person => person.id" :overscan="6" class="h-80" role="list" aria-label="Matching members">
    <template #default="{ item }"><div role="listitem" class="h-12">{{ item.name }}</div></template>
  </VirtualList>
</template>

Key API

The most useful props, models, slots, events, and methods for this component.

Key API for VirtualList
NameTypeDefault or requirement
items / itemSize / getKeyarray / number / functionrequired
overscannumber4
scrollToIndex(index, options)exposed method

VirtualList handles rendering and scrolling. Add list or grid roles and labels that match your content, as shown in the example.

Implementation notes

  • The rendered row height must match itemSize; variable-height rows need a different strategy.
  • Use stable keys from the records rather than their changing positions after sorting or filtering.

Accessibility and localization

  • Choose list or grid roles to match the rendered content, and consider a nonvirtual view when users need all items available at once.
Weini UIDesign system · Vue component library · Free and public