@glhrmoura/vue-virtualize

Headless virtualization for Vue 3 lists, grids, and tables
1,371
Install
npm install @glhrmoura/vue-virtualize
Latest Version:0.2.2
Vue:3
License:MIT
Last Updated:Sep 29, 2026
Links: npm
Maintainer: glhrmoura

Vue Virtualize

NPM Version License

Headless virtualization for Vue 3 lists, grids, and tables.

Documentation

Vue Virtualize: Documentation

Install

yarn add @glhrmoura/vue-virtualize
npm install @glhrmoura/vue-virtualize

Vue 3.5+ is a peer dependency.

Usage

VirtualList
<script setup lang="ts">
import { VirtualList } from '@glhrmoura/vue-virtualize'
import { shallowRef } from 'vue'

const users = shallowRef(
  Array.from({ length: 100_000 }, (_, index) => ({
    id: index + 1,
    name: `User ${index + 1}`,
  })),
)
</script>

<template>
  <VirtualList :items="users" :height="500" :get-item-key="(user) => user.id">
    <template #default="{ item, index }">
      <UserCard :user="item" :index="index" />
    </template>
  </VirtualList>
</template>

estimateSize defaults to 48. A number locks a fixed item size (count × size, best for huge lists). Pass a function when rows have variable height so the list can measure mounted elements without clipping. Container scroll needs a height (unless window-scroll).

Horizontal list
<VirtualList
  :items="slides"
  horizontal
  :width="640"
  :height="180"
  :estimate-size="200"
  :get-item-key="(slide) => slide.id"
>
  <template #default="{ item }">
    <SlideCard :slide="item" />
  </template>
</VirtualList>
Window scroll

Use the document/window as the scroller. No fixed container height required.

<VirtualList
  :items="posts"
  window-scroll
  :estimate-size="72"
  :get-item-key="(post) => post.id"
  @visible-range-change="onRange"
  @scroll-end="onScrollEnd"
>
  <template #default="{ item }">
    <PostCard :post="item" />
  </template>
</VirtualList>
Reverse / chat feed

reverse starts at the end, sticks to the bottom when new items append, and keeps scroll stable when older items are prepended (requires stable get-item-key).

<VirtualList
  :items="messages"
  reverse
  :height="480"
  :estimate-size="56"
  :get-item-key="(message) => message.id"
  @visible-range-change="maybeLoadOlder"
>
  <template #default="{ item }">
    <MessageBubble :message="item" />
  </template>
</VirtualList>
VirtualGrid
<VirtualGrid
  :items="photos"
  :column-count="4"
  :estimate-row-size="120"
  :estimate-column-size="180"
  :height="500"
>
  <template #default="{ item, rowIndex, columnIndex }">
    <PhotoCard :photo="item" :row="rowIndex" :column="columnIndex" />
  </template>
</VirtualGrid>
VirtualTable

Sticky header is on by default. Pin columns with pin: 'start' | 'end' (first column, actions, etc.). Give pinned cells a solid background so content does not show through.

const columns = [
  { id: 'name', header: 'Name', width: 220, pin: 'start' },
  { id: 'email', header: 'Email', width: 280 },
  { id: 'actions', header: '', width: 120, pin: 'end' },
]
<VirtualTable :rows="users" :columns="columns" :estimate-row-size="44" :height="500">
  <template #cell="{ row, column, value }">
    <span>{{ value }}</span>
  </template>
</VirtualTable>
useVirtualizer
const { virtualItems, totalSize, scrollToIndex } = useVirtualizer({
  count: items.length,
  container,
  estimateSize: () => 52,
  overscan: 4,
  windowScroll: false,
  reverse: false,
  onVisibleRangeChange: (range) => {
    if (range.endIndex >= range.count - 8) loadMore()
  },
  onScrollEnd: () => {},
})

Events / callbacks

Name Where When
visibleRangeChange / onVisibleRangeChange components / composables Visible index window changes
scrollEnd / onScrollEnd components / composables Scroll settles (~120ms idle)

API Reference

Export Role
VirtualList One axis, vertical or horizontal, optional window-scroll / reverse
VirtualGrid Flat array, columnCount, row and column estimates
VirtualTable rows, columns, sticky header, pinned columns
useVirtualizer Custom markup driven by virtualItems and totalSize
useVirtualList Headless list adapter
useVirtualGrid Headless grid adapter
useVirtualTable Headless table adapter

License

MIT

Copyright (c) Guilherme Moura

Related Packages

virtua

A zero-config, fast and small virtual list, grid and masonry component for React...

29,130,889 3,753
vue-grid-canvas

A vue component, it can generate a similar excel form for you

2,337 21
stk-table-vue

High performance realtime virtual table for vue3 and vue2.7

15,030 196
@pdanpdan/virtual-scroll

Virtual scrolling for Vue 3: only the rows near the viewport are in the DOM.

8,720 13
vue-virtual-grid

_Virtual Scrolling Grid made for VueJS based on CSS grids._

17,718 54

Version History

Version Released License
0.2.2 MIT
0.2.1 MIT
0.2.0 MIT
0.1.5 MIT
0.1.4 MIT