Skip to content

VirtualGrid

A virtual grid that arranges items in rows and columns. Column count can be fixed or auto-calculated from columnWidth and the container width.

Props

items

T[]

Data array.

columns

number · default: 0

Fixed column count. Pass 0 to auto-compute from columnWidth.

columnWidth

number · default: 200

Cell width for auto-column calculation.

rowHeight

number · default: 200

Cell height in px — the initial estimate when dynamicRowHeight is on.

gap

number · default: 8

Gap between cells in px.

keyField

string · default: 'id'

Key field.

overscan

number · default: 2

Extra rows outside the viewport.

isLoading

boolean · default: false

Shows skeleton when items is empty.

motionBlur

boolean · default: false

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

dynamicRowHeight

boolean · default: false

Measure each row's actual height via ResizeObserver instead of a fixed rowHeight (row height = max of that row's cells).

Slots

default

Scope: { item: T, index: number, row: number, col: number }

Cell content.

empty

Scope: No scope

Shown when items is empty.

skeleton

Scope: No scope

Shown when empty and isLoading.

Emits

scroll

Payload: Event

visible-range-change

Payload: { start: number; end: number }

Exposed API

scrollTo(index, options?)

Scroll to a cell index. Unlike the other components' scrollTo, VirtualGrid derives the target row from index and doesn't take an align parameter.

getScrollElement()

Returns the element that actually scrolls — pair with VirtualScrollbar.

ts
gridRef.value?.scrollTo(index)
gridRef.value?.scrollTo(index, { behavior: 'smooth' })
gridRef.value?.getScrollElement() // pair with VirtualScrollbar

Example

vue
<script setup lang="ts">
import { VirtualGrid } from 'vue-virtual-scroller-kit'

interface Photo {
  id: number
  url: string
  title: string
}

const photos: Photo[] = Array.from({ length: 10_000 }, (_, i) => ({
  id: i,
  url: `https://picsum.photos/seed/${i}/200/200`,
  title: `Photo ${i + 1}`,
}))
</script>

<template>
  <VirtualGrid
    :items="photos"
    :column-width="220"
    :row-height="220"
    :gap="12"
    style="height: 600px"
  >
    <template #default="{ item }">
      <div class="photo-card">
        <img :src="item.url" :alt="item.title" />
        <p>{{ item.title }}</p>
      </div>
    </template>
  </VirtualGrid>
</template>

Dynamic row height — when cell content height varies (e.g. captions of different lengths), row height is measured per row instead of fixed:

vue
<VirtualGrid
  :items="photos"
  :column-width="220"
  :row-height="220"
  dynamic-row-height
  :gap="12"
  style="height: 600px"
>
  <template #default="{ item }">
    <div class="photo-card" style="height: auto">
      <img :src="item.url" :alt="item.title" />
      <p>{{ item.title }}</p>
      <p v-if="item.caption" class="photo-card__caption">{{ item.caption }}</p>
    </div>
  </template>
</VirtualGrid>

With dynamicRowHeight, cells no longer get a fixed height from the grid — give them height: auto (or leave height unset) so their content determines the row's real height.