API Reference

Complete documentation for @pdanpdan/virtual-scroll.

Introduction

@pdanpdan/virtual-scroll is a high-performance Vue 3 virtual scroll library designed to handle massive lists with ease. It supports vertical, horizontal, and bidirectional (grid) scrolling, dynamic item sizes using ResizeObserver, and full support for Right-to-Left (RTL) layouts.

Performance

Virtualization keeps the DOM small by rendering only the items in the viewport (plus a configurable buffer), so scrolling stays responsive regardless of dataset size. Scroll handling and range calculations are optimized for every sizing mode; the biggest wins come from the configuration choices below.

Scroll performance & scrollbar dragging

Browsers throttle native scrollbar dragging: while the thumb is dragged, the scroll position advances in coarse per-frame steps. As a result, the content (and the virtualized items that follow it) can lag noticeably behind the thumb on large drags, even when the target area was already rendered. Wheel, touch, and keyboard scrolling are not affected.

To keep scrollbar dragging instant and 1:1 with the pointer, it is strongly suggested to use the built-in virtual scrollbars:

  1. Enable them with the virtualScrollbar prop on VirtualScroll (they are also enabled automatically for lists beyond the browser size limit).
  2. Match your design using the --vs-scrollbar-* CSS variables, or take full control of the scrollbar UI with the scrollbar scoped slot.

The native scrollbar is hidden automatically (.virtual-scroll--hide-scrollbar) whenever virtual scrollbars are active - no extra CSS is needed.

Note: virtual scrollbars are not available when the scroll container is the window or the body - use an element container.

Other Performance Advice

  • Prefer fixed sizes. A numeric itemSize / columnWidth gives O(1) range math; arrays and functions use O(log n) Fenwick-tree lookups; dynamic (measured) sizes are the most expensive and re-measure with ResizeObserver. See the Sizing Guide below.
  • Skip per-row data for uniform lists. A numeric itemSize / columnWidth allocates no per-row storage and positions rows arithmetically, so index-only lists (sparse items, e.g. new Array(10_000_000)) keep memory flat at any scale - render row content from the slot's index instead of item.
  • Keep buffers modest. bufferBefore / bufferAfter (default 5) trade rendering cost for scrolling smoothness - a larger buffer means more DOM nodes and more work per frame.
  • Keep item content cheap. The item slot is re-rendered on scroll; avoid heavy markup, images, or effects inside items.
  • Use an element container for scrollable UIs instead of the window or body: it isolates scrolling, enables virtual scrollbars, and avoids full-page layout work.
  • Use ssrRange to pre-render the initial viewport and skip the first measure/scroll pass on slow devices.
  • Massive lists are handled automatically. Beyond the browser's ~10,000,000 px limit the library switches to coordinate scaling - virtual content units (VU) are mapped onto the browser's display units (DU) - so no extra configuration is needed, except with window/body containers, where coordinate scaling and virtual scrollbars are disabled.

Authoring Content for Virtualized Lists

Virtualization reuses a small window of DOM nodes: rows mount as they enter the viewport and unmount when they leave. Most authoring works exactly like any other Vue list, but content that relies on being mounted once, loads late, or grows after mount needs extra care to stay smooth and correct.

  • Keep row state in the model, not the DOM. Selection, expanded rows, likes, cart state, or carousel positions belong in your data (keyed by item id) or a store - never in the row's own DOM. Rows are recycled by index, so anything stored in the element disappears when the row scrolls away and would also leak across items.
  • Make row rendering idempotent. The item slot re-renders whenever the item enters the window (and again on scroll). Rendering the same item twice must produce the same result - no one-time setup, no listeners bound per mount that are never removed, no DOM the component does not own.
  • Prefer delegated or component-scoped events. Interactions on rows should bubble to the container or live in the item component's own handlers. State updates flow back into the model and the visible rows re-render from it - never mutate row content from outside.
  • Reserve space for media. Give images/videos an explicit width/height or aspect-ratio. Media that loads with unknown dimensions resizes the row after mount, which the engine measures and corrects - but repeated late growth causes visible jumps and extra work.
  • Do not combine native loading="lazy" with virtualization. The visible window is already the only mounted content; native lazy-loading adds browser heuristics on top of a scroll container whose content keeps changing. This can starve or delay the very images on screen. Load visible images eagerly, or via your own bounded, low-priority prefetch window ahead of the viewport.
  • Prefetch offscreen content in bounded, deprioritised windows. If rows show remote data (images, fetched text), prefetch only a small range past the viewport and give it lower priority than what is visible, so on-screen content is never starved.
  • Avoid content that mounts asynchronously and changes row height late. Dynamic heights are fully supported (ResizeObserver measures and the layout self-corrects), but the smoothest experience comes from content whose size is stable or reserved up front - especially in lists that also use snapping or sticky items.

The chat, gallery, blog, and data-browser examples in the playground demonstrate these patterns with real content: dynamic bubbles, media cards, grouped headers, and interactive rows.

Sizing Guide

The library offers flexible ways to define item and column sizes. Calculations are optimized based on the type of sizing used.

TypeitemSize / columnWidthPerfDescription
FixednumberBestUniform size for all items. Calculations are O(1).
Array (Circular Pattern)number[]GreatRepeating size patterns from array (e.g. [50, 100]). O(log n).
Function(item, idx) => numberGoodKnown but variable sizes. No ResizeObserver overhead unless measured size differs.
Dynamic0, null, undefinedFairSizes measured via ResizeObserver after rendering.

Key Features

Bidirectional Scrolling

Virtualize both rows and columns for massive data grids.

Dynamic Item Sizes

Automatic measurement via ResizeObserver for precise scrolling.

RTL Support

Automatic direction detection and correct coordinate mapping for RTL layouts.

Native Window Scroll

Use the browser window/body as the scroll container.

Sticky Headers/Footers

iOS-style pushing headers for segmented lists and groups.

Scroll Restoration

Maintains position when prepending items (perfect for chat).

SSR & Hydration

Full support for server-side rendering and client hydration.

Massive List Support

Handles 10M+ items via automatic coordinate scaling (except for window/body containers).

Virtual Scrollbars

Fully customizable virtual scrollbars that replace native ones.

Scroll Snapping

Auto-align items to viewport edges or center (start, center, end, auto).

Circular Sizing Patterns

Pass arrays to define repeating size patterns for items or columns.

Masonry Layout

Real masonry in one scroll container: responsive columns, canonical oracle heights, anchored reflow, bounded DOM at any scale.

Quick Start

Install the package using your favorite package manager:

pnpm add @pdanpdan/virtual-scroll

Basic usage in a Vue component:

<script setup>
import { VirtualScroll } from "@pdanpdan/virtual-scroll";
import "@pdanpdan/virtual-scroll/style.css";

const items = Array.from({ length: 1000 }, (_, i) => ({ id: i, name: `Item ${i}` }));
</script>

<template>
<VirtualScroll :items="items" :item-size="50" class="h-96">
  <template #item="{ item }">
    <div class="h-12 flex items-center px-4 border-b border-base-200">
      {{ item.name }}
    </div>
  </template>
</VirtualScroll>
</template>

Usage Modes

Compiled Component

Recommended for most projects. Uses pre-compiled JavaScript.

import { VirtualScroll } from "@pdanpdan/virtual-scroll";
import "@pdanpdan/virtual-scroll/style.css";

  • Compatible with all modern bundlers.
  • Note: Manual CSS import is required.

Original Vue SFC

Import raw source for custom compilation.

import VS from "@pdanpdan/virtual-scroll/VirtualScroll.vue";

  • Enables better tree-shaking in your build.
  • Styles handled by your Vue loader.

CDN Usage

Use directly in the browser without a build step.

<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@pdanpdan/virtual-scroll/dist/virtual-scroll.css">
<script src="https://cdn.jsdelivr.net/npm/@pdanpdan/virtual-scroll/dist/index.js"></script>

  • No installation required.
  • Available via window.VirtualScroll.

Extensions

The library uses a highly modular architecture powered by extensions. Extensions can tap into the core lifecycle to add features like RTL support, snapping, or custom loading logic without bloating the core engine.

Built-in Extensions

Usage with Composable

When using the low-level useVirtualScroll composable:

import {
  useVirtualScroll,
  useRtlExtension,
  useSnappingExtension
} from '@pdanpdan/virtual-scroll';

const vs = useVirtualScroll(props, [
  useRtlExtension(),
  useSnappingExtension()
]);

VirtualScroll Component

The VirtualScroll component is the primary way to use this library. It provides a declarative Vue interface for virtualizing large lists and grids, handling all rendering, recycling, and scroll logic automatically.

Props

Core Configuration

PropTypeDefaultDescription
itemsT[]-The array of items to render. Required. Entries may be undefined (e.g. new Array(n) for index-only lists): every index in range renders and the slot item is undefined for holes; only the visible window is ever accessed.
itemSizenum | arr | fn | null40Fixed size, circular array pattern, or function. See the Sizing Guide.
direction'vertical' | 'horizontal' | 'both''vertical'The scroll direction.
gapnumber0Spacing between items (vertical or horizontal).

Grid Configuration (only for direction="both")

PropTypeDefaultDescription
columnCountnumber0Number of columns for grid mode.
columnWidthnum | arr | fn | null100Width for columns in grid mode (supports fixed, array pattern, or function).
columnGapnumber0Spacing between columns.

Features & Behavior

PropTypeDefaultDescription
stickyIndicesnumber[][]Indices of items that should remain sticky. When stickyHeader or stickyFooter are enabled, they stick below or above them.
stickyHeader / stickyFooterbooleanfalseIf true, the header or footer size is measured and added to padding. Sticky stickyIndices items align below or above them.
ssrRange{start, end, ...}-Range of items to pre-render. See SSR Support.
loadingbooleanfalseWhile true, reveals the #loading slot (the slot stays mounted when provided and is hidden via CSS while false) and suppresses repeated load events.
loadDistancenumber200Distance from the end to trigger the load event.
snapbool | SnapModefalseAutomatically align to nearest item after scroll stops. See SnapMode.
virtualScrollbarbooleanfalseWhether to force the use of virtual scrollbars. Automatically enabled for massive lists. Note: Disabled when using window or body as the container.
restoreScrollOnPrependbooleanfalseMaintain scroll position when items are added to the top.
containerHTMLElement | WindowhostRefThe scrollable container. Defaults to the component's root element. Pass window or document.body to scroll the page.
initialScrollIndexnumber-Index to jump to on mount.
initialScrollAlignScrollAlignment | Options'start'Alignment for initial index.

Accessibility

PropTypeDefaultDescription
rolestring'list' | 'grid'ARIA role for the container. Automatically detected based on direction.
ariaLabelstring-Accessible label for the scroll container.
ariaLabelledbystring-ID of the element that labels the scroll container.
itemRolestring-ARIA role for each item. Set to 'none' to manually apply roles using getItemAriaProps.

ScrollAlignment

Controls the item's final position in the viewport: 'start' | 'center' | 'end' | 'auto'.

Advanced & Performance

PropTypeDefaultDescription
containerTagstring'div'HTML tag for the scroll container, e.g. for semantic list markup. For tabular data use the VirtualScrollTable component.
wrapperTagstring'div'HTML tag for the items wrapper. Combine 'ul'/'ol' with itemTag: 'li' for semantic lists. Tables should use the VirtualScrollTable component.
itemTagstring'div'HTML tag for each virtualized item (e.g. 'li'). For table rows use VirtualScrollTable.
headerTagstring'div'HTML tag for the header slot wrapper (e.g. 'header'). Tables use VirtualScrollTable, whose header slot renders the <thead>.
footerTagstring'div'HTML tag for the footer slot wrapper (e.g. 'footer'). Tables use VirtualScrollTable, whose footer slot renders the <tfoot>.
scrollPaddingStart / Endnum | {x, y}0Additional padding for scroll offsets.
bufferBefore / bufferAfternumber5Number of items to render outside the viewport.
defaultItemSizenumber40Estimated size for items before measurement.
defaultColumnWidthnumber100Estimated width for columns before measurement.
debugbooleanfalseEnables debug mode (visible offsets and indices).
* For a full list of props including advanced configuration, see the VirtualScrollProps interface.

Accessibility (ARIA)

The component automatically manages ARIA roles and attributes to ensure screen readers can navigate virtualized content. Common roles like tree, listbox, and menu are also supported.

Role PropDefault Item RoleBehavior
list (default)listitemStandard 1D list.
gridrow2D data grid or table.
treetreeitemHierarchical structure.
listboxoptionSelectable list.
menumenuitemNavigation menu.

Slots

#item

Scoped slot for individual items.

  • item: T: The data item from the source array (undefined for holes in sparse or index-only datasets).
  • index: number: The original 0-based index of the item.
  • isSticky: boolean: true if the item is configured to be sticky via stickyIndices.
  • isStickyActive: boolean: true if the item is currently stuck at the threshold.
  • isStickyActiveX / Y: boolean: true if the item is stuck at the horizontal or vertical threshold.
  • offset: { x, y }: Calculated physical position in display units (DU).
  • columnRange: ColumnRange: Precise indices and paddings for visible columns.
  • getColumnWidth: (index: number) => number: Helper to get the calculated width of any column.
  • getItemAriaProps: (index: number) => object: Helper to get ARIA attributes for an item (e.g. role="listitem", aria-posinset).
  • getCellAriaProps: (index: number) => object: Helper to get ARIA attributes for a cell (e.g. role="gridcell", aria-colindex).
  • gap: number: Vertical gap between items.
  • columnGap: number: Horizontal gap between columns.

#scrollbar

Scoped slot for custom scrollbar implementation.

  • axis: 'vertical' | 'horizontal': The scrollbar axis.
  • positionPercent: number: Current scroll position (0 to 1).
  • viewportPercent: number: Viewport as percentage of total size.
  • thumbSizePercent: number: Calculated thumb size (0 to 100).
  • thumbPositionPercent: number: Calculated thumb position (0 to 100).
  • trackProps: object: Attributes and listeners for the track element.
  • thumbProps: object: Attributes and listeners for the thumb element.
  • isDragging: boolean: Whether the thumb is currently being dragged.
  • scrollbarProps: object: Grouped properties for VirtualScrollbar.
    • axis: 'vertical' | 'horizontal'
    • totalSize: number
    • position: number
    • viewportSize: number
    • scrollToOffset: (offset: number) => void
    • containerId: string
    • isRtl: boolean
    • ariaLabel: string

#header / #footer

Content rendered above or below the virtualized items. Can be made sticky using the stickyHeader / stickyFooter props.

#loading

Always rendered when provided - hidden via the virtual-scroll-loading--hidden class (visibility: hidden) while loading is false - so it reserves its space and the End key can include its size in the scroll target. While loading is true, further load events are suppressed. Only provide the slot while a load is expected: once there is no more data, stop passing it (e.g. v-if="hasMore" on <template #loading>) so the reserved space disappears.

ScrollbarSlotProps

Properties passed to the 'scrollbar' scoped slot.

<template>
<VirtualScroll :items="items" direction="both" virtual-scrollbar>
  <template #scrollbar="{ trackProps, thumbProps, axis }">
    <!-- Vertical Track -->
    <div v-if="axis === 'vertical'" v-bind="trackProps" class="w-2 bg-base-300">
      <div v-bind="thumbProps" class="bg-primary rounded" />
    </div>

    <!-- Horizontal Track -->
    <div v-else v-bind="trackProps" class="h-2 bg-base-300">
      <div v-bind="thumbProps" class="bg-secondary rounded" />
    </div>
  </template>
</VirtualScroll>
</template>
PropertyTypeDescription
axis'vertical' | 'horizontal'The scrollbar axis.
positionPercentnumberScroll position percentage (0-1).
viewportPercentnumberViewport percentage of total (0-1).
thumbSizePercentnumberCalculated thumb size percentage (0-100).
thumbPositionPercentnumberCalculated thumb position percentage (0-100).
trackPropsRecord<string, unknown>Attributes/listeners for the track. Bind with v-bind="trackProps". Includes class and style.
thumbPropsRecord<string, unknown>Attributes/listeners for the thumb. Bind with v-bind="thumbProps". Includes class and style.
scrollbarPropsVirtualScrollbarPropsGrouped props for the VirtualScrollbar component: axis, totalSize, position, viewportSize, scrollToOffset, containerId, isRtl, ariaLabel. Useful for <VirtualScrollbar v-bind="scrollbarProps" />.
isDraggingbooleanWhether the thumb is currently being dragged.

Events

EventPayloadDescription
scrollScrollDetails<T>Emitted on every scroll position change.
load'vertical' | 'horizontal'Triggered when the user scrolls within loadDistance of the end.
visibleRangeChange{ start, end, colStart, colEnd }Emitted when the set of rendered indices changes.

Keyboard Navigation

The container is keyboard-accessible when focused (tabindex="0"). It supports standard navigation keys:

HomeScroll to the very beginning (Index 0,0).
EndScroll to the very last row and column, including the loading slot size when a #loading slot is present.
PgUp / PgDnScroll by one full viewport: target is the first visible item minus one / the last visible item plus one.
Scroll vertically by item height (respects snap mode).
Scroll horizontally by column width (respects snap mode).

CSS Classes

ClassDescription
.virtual-scroll-containerThe root scrollable container element.
.virtual-scroll-wrapperWraps rendered items and provides total scrollable dimensions.
.virtual-scroll-itemApplied to each individual rendered item. Use for general item styling.
.virtual-scroll-header / .virtual-scroll-footerContainers for header and footer slots.
.virtual-scroll-loadingContainer for the loading slot.
.virtual-scroll-loading--hiddenApplied to the loading slot while loading is false: hides it with visibility: hidden while keeping its space (the slot is always rendered when provided).
.virtual-scroll--vertical / --horizontal / --bothDirection modifiers applied to the root container.
.virtual-scroll--hydratedApplied after client-side mount and hydration is complete.
.virtual-scroll--windowApplied when scrolling via the global window object.
.virtual-scroll--tableApplied by the VirtualScrollTable component.
.virtual-scroll--stickyApplied to items that are currently stuck to the viewport edge.
.virtual-scroll--debugVisible when debug prop is active.
.virtual-scroll--hide-scrollbarApplied when virtual scrollbars are enabled or content is massive.

CSS Variables

The default VirtualScrollbar can be styled using the following CSS variables:

VariableDefault (Light/Dark)Description
--vs-scrollbar-bgrgba(230,230,230,0.9) / rgba(30,30,30,0.9)Track background color.
--vs-scrollbar-thumb-bgrgba(0,0,0,0.3) / rgba(255,255,255,0.3)Thumb background color.
--vs-scrollbar-thumb-hover-bgrgba(0,0,0,0.6) / rgba(255,255,255,0.6)Thumb background on hover/active.
--vs-scrollbar-size8pxWidth (vertical) or height (horizontal) of the scrollbar.
--vs-scrollbar-radius4pxBorder radius for track and thumb.
--vs-scrollbar-cross-gapvar(--vs-scrollbar-size)Size of gap to use where scrollbars meet.
--vs-scrollbar-has-cross-gap0If gap should be shown where scrollbars meet.

Exposed Members

The VirtualScroll component exposes several reactive properties and methods from the underlying logic. You can access these via a template ref.

Properties

Methods

VirtualScrollTable Component

For tabular data use the dedicated VirtualScrollTable component: it renders semantic <table>/<tbody>/<tr> structure, keeps the virtual offsets with spacer rows in real table flow (flowTable) or with absolute rows (fallback), measures dynamic row heights, and exposes the header, footer and item slots. When the table is wider than its container it gets its own horizontal virtual scrollbar. See the Flow Table example and the Table example. Scroll snapping is supported in table mode: flow rows and absolute rows snap to the same row offsets as list mode.

Props

PropTypeDefaultDescription
flowTablebooleanfalseRender rows in real table flow between spacer rows instead of absolutely positioning them. Vertical lists only; row heights may be uniform (itemSize) or dynamic (measured). Unsupported configurations fall back to absolute rows.
autoSizeColumnsbooleanfalsePin column widths from the first rendered window via a colgroup with table-layout: fixed, so later windows never reflow the columns. Requires flowTable and equal direct-cell counts across rows.
columnWidthsnumber[]-Explicit column widths (px) pinned via the colgroup; takes precedence over autoSizeColumns.
stickyHeader / stickyFooterbooleanfalseMeasure and reserve the header/footer slots; the row groups stick to the viewport edges.
virtualScrollbarbooleanfalseForce virtual scrollbars; vertical and, when the table overflows horizontally, horizontal bars are rendered.

Shared API with VirtualScroll

VirtualScrollTable is the same virtualization component with table semantics - almost everything on the VirtualScroll component still applies:

  • Slots: the same item, header, footer, loading and scrollbar slots. The item slot receives the same scoped props (item, index, sticky flags, offsets, column range helpers). In table mode the slot content is rendered inside the row elements provided by the component, so items slot in <td> cells and header/footer slot in <th>/<td> cells. See Slots.
  • Events: identical scroll, visibleRangeChange and load events. See Events.
  • Exposed instance (via ref): the same methods and state - scrollToIndex, scrollToOffset, refresh, updateItemSizes, stopProgrammaticScroll, scrollDetails, isHydrated, getItemOffset/getItemSize and the rest - plus table constants (isTable: true, itemTag: 'tr', containerTag: 'table', wrapperTag: 'tbody'). See Methods.
  • Props: the shared base surface applies unchanged - items, itemSize (uniform or dynamic), bufferBefore/bufferAfter, initialScrollIndex/initialScrollAlign, restoreScrollOnPrepend, infiniteScroll-related props (loadDistance, loading), ssrRange, debug, role/ARIA props and rtl. Tag customization (containerTag/wrapperTag/itemTag) lives on VirtualScroll for semantic lists (e.g. ul/ol > li). On VirtualScrollTable the container, wrapper and row elements are fixed to their semantic table tags - use this component for tabular data. Grid/gap/sticky-index/scroll-padding props are not part of the table flow surface (they fall back to absolute rows), and direction is vertical. See Props.
  • Behavior & theming: the same engine wiring - keyboard navigation, coordinate scaling, custom scrollbar slot support, sticky measurements, prepend restoration - and the same CSS classes and CSS variables. The snap caveat above applies.

VirtualScrollMasonry Component

VirtualScrollMasonry renders a real masonry grid inside a single native scroll container: the column count and a fractional column width are derived from the container width, cards are placed greedily on the shortest column through segment-snapshotted column frontiers, and only the window around the scroll position is mounted (plus one segment of overscan) - the DOM stays bounded no matter the dataset size or how far the user jumps.

Heights come from the deterministic itemHeight oracle by default (canonical layout: far scrollToIndex calls land on the exact greedy position without ever mounting the path, unvisited segments are priced arithmetically, and the total is exact once the frontier chain reaches the end). With measuredHeights, mounted cards are measured instead and the measured boxes drive the layout (local determinism: reproducible per measurement history). Container reflows (resize, column-geometry changes, dataset replacement) re-anchor the topmost visible card at its screen offset instead of holding a raw pixel position. See the Masonry example.

Props

PropTypeDefaultDescription
itemsT[]RequiredArray of data items to virtualize. May be sparse (new Array(n)): holes render and the slot item is undefined for them; only the rendered window is accessed.
itemHeightfn(item, index, columnWidth)RequiredCanonical height oracle in px. MUST be deterministic - the same (index, columnWidth) must always return the same height - because placements are committed to a frontier chain and replayed from stored snapshots. Non-finite results fall back to 40; finite non-positive results clamp to 1.
targetColumnWidthnumber240Desired column width in px. The column count is derived from the container width so columns land as close as possible to this target; the actual width is fractional so the gutters divide the width exactly.
minColumns / maxColumnsnumber1 / 10Column count bounds for the responsive reflow.
measuredHeightsbooleanfalseMeasure mounted cards with a ResizeObserver and drive the layout from the measured boxes instead of the oracle. Off: canonical oracle layout, nothing is measured. On: cards size to their content (the oracle height becomes the pre-measure minimum, so estimate-sized first mounts do not re-flow) and every accepted measurement re-lays-out with the topmost visible card re-anchored.
gapnumber10Spacing between cards in px, applied both between columns and between rows of the layout.
segmentSizenumber500Items per layout segment - the cadence at which the real column frontier is snapshotted. Larger segments store less frontier state but make each layout step cross more items.
virtualScrollbarbooleantrueRender the overlay virtual scrollbar (the native one is hidden while enabled). Hidden automatically when the content fits the viewport.
role / itemRolestring'list' / 'listitem'ARIA roles for the cards wrapper and each card (grid/tree/listbox/menu wrappers map to their child roles). Set itemRole: 'none' to disable role assignment.
ariaLabel / ariaLabelledbystring-Accessible label for the scroll container (the container role becomes region when labelled).
debugbooleanfalseOutline rendered card bounds and overlay a geometry badge (#index (x, y)) per card.

Item Slot

PropTypeDescription
item / indexT | undefined / numberThe original data item and its 0-based dataset index (undefined for sparse holes).
columnnumberThe 0-based column the card was placed into.
x / ynumberCard offset in px relative to the cards wrapper (the component positions the card itself via translate).
width / heightnumberCard size in px - the resolved column width and the layout-resolved height (oracle height in canonical mode; measured height with measuredHeights). In canonical mode render content to exactly fill the oracle height; with measuredHeights cards size to their content.

Exposed Members & Events

MemberTypeDescription
scroll (event)MasonryScrollDetails<T>Emitted on scroll and every layout change: rendered items (card geometry), currentIndex/currentEndIndex, range, scrollOffset/displayScrollOffset (y), viewportSize, totalSize, columnRange and scrolling flags.
scrollDetailsMasonryScrollDetails<T>Current scroll state (same shape as the scroll event payload).
columns / columnWidthnumberLive resolved column count and column width in px (0 until the container is measured) - e.g. for srcset candidates or text budgets.
totalHeight / totalHeightExactnumber / booleanContent height in px - extrapolated from the known frontier prefix until the chain reaches the end, then exact. End-anchored scrolls re-clamp as estimates settle.
scrollToIndexfn(index?, options?)Scroll to a card with align ('start' | 'center' | 'end' | 'auto'), behavior and dryRun; far jumps land on the exact canonical position.
scrollToOffsetfn(offset?, options?)Scroll to a pixel offset (use ±Infinity for the very end/start; end intents follow the content as totals settle).
refreshfn()Drop every cached frontier and re-layout from the current anchor - after in-place item edits or oracle changes.

Sizing contract & limitations

  • In canonical mode cards must render at exactly the oracle height - reserve media space (aspect-ratio, fixed model heights) and never rely on DOM measurement. With measuredHeights, cards size to their content and only mounted cards are measured (unmounted regions keep the oracle estimate).
  • Vertical axis only: no RTL, horizontal, or both mode and no coordinate scaling - very tall datasets stay below the browser's ~10M px scroll limit.
  • Not available for SSR pre-rendering: content mounts after the container is measured. Extensions/snap/sticky/loading of the list engine do not apply.

VirtualScrollbar Component

The VirtualScrollbar component provides a cross-browser consistent scrollbar that can be used independently or within the VirtualScroll component. Check out the Independent Scrollbars example to see it in action without virtualization.

<script setup>
import { VirtualScrollbar } from "@pdanpdan/virtual-scroll";
import { ref } from "vue";

const scrollX = ref(0);
const scrollY = ref(0);
</script>

<template>
<div class="relative overflow-hidden h-96">
  <!-- Vertical Scrollbar -->
  <VirtualScrollbar
    axis="vertical"
    :total-size="10000"
    :viewport-size="400"
    :position="scrollY"
    @scroll-to-offset="val => scrollY = val"
  />

  <!-- Horizontal Scrollbar -->
  <VirtualScrollbar
    axis="horizontal"
    :total-size="10000"
    :viewport-size="800"
    :position="scrollX"
    @scroll-to-offset="val => scrollX = val"
  />
</div>
</template>

Props

PropTypeDefaultDescription
axis'vertical' | 'horizontal''vertical'The axis of the scrollbar. Defaults to 'vertical'.
totalSizenumber-Total size of the scrollable content in pixels. Required.
viewportSizenumber-Size of the visible viewport in pixels. Required.
positionnumber-Current scroll position in pixels. Required.
scrollToOffset(offset: number) => void-Optional callback invoked with the new offset on user interaction, right before the scrollToOffset event fires.
containerIdstringundefinedID of the container element for accessibility.
isRtlbooleanfalseWhether the scrollbar is in Right-to-Left (RTL) mode.
ariaLabelstring-Accessible label for the scrollbar.

Events

EventPayloadDescription
scroll-to-offsetnumberEmitted when the user interacts with the scrollbar to change position.

Composables

useVirtualScroll

Provides the core virtualization logic. Recommended for advanced use cases or when building custom wrappers.

import { useVirtualScroll } from '@pdanpdan/virtual-scroll';
import { computed, ref } from 'vue';

const items = ref([...]);
const props = computed(() => ({
items: items.value,
itemSize: 50,
direction: 'vertical'
}));

const {
renderedItems,
scrollDetails,
totalHeight,
scrollToIndex
} = useVirtualScroll(props);

Parameters

Accepts a MaybeRefOrGetter to a VirtualScrollProps object.

Return Value

MemberTypeDescription
renderedItemsRef<RenderedItem<T>[]>List of items to render in the current buffer.
scrollDetailsRef<ScrollDetails<T>>Full reactive state of the virtual scroll system.
columnRangeRef<ColumnRange>Visible columns and their associated paddings.
totalWidth / totalHeightRef<number>Calculated total size of the scrollable content area (DU).
renderedWidth / renderedHeightRef<number>Total dimensions to be rendered in the DOM (clamped to browser limits, DU).
isHydratedRef<boolean>true when the component is mounted and hydrated.
isRtlRef<boolean>true if the scroll container is in Right-to-Left mode.
scaleX / scaleYRef<number>Current coordinate scaling factors (VU / DU).
componentOffset{ x: Ref<number>, y: Ref<number> }Absolute offset of the component in its container (DU).
scrollbarOffsetReactive<{ x: number; y: number }>Inline-start/block-start padding of the scroll container (DU), used to align the virtual scrollbar overlay with the scrollport.
scrollToIndexFunctionProgrammatic scroll to a specific index. End-anchored scrolls re-clamp while settling measurements move the real end, so a first jump to the end lands flush on dynamic lists.
scrollToOffsetFunctionProgrammatic scroll to a pixel offset.
stopProgrammaticScrollFunctionCancel any active smooth scroll animation.
handleScrollCorrectionFunctionAdjust scroll position to compensate for measurement changes.
refreshFunctionResets all measurements and state.
updateItemSizeFunctionRegister a manual item measurement.
updateItemSizesFunctionRegister multiple manual item measurements.
updateHostOffsetFunctionForce update the container's relative position.
updateDirectionFunctionManually trigger direction (LTR/RTL) detection.
getColumnWidthFunctionHelper to get a column's width.
getRowHeightFunctionHelper to get a row's height.
getRowOffsetFunctionHelper to get a row's virtual offset (VU).
getColumnOffsetFunctionHelper to get a column's virtual offset (VU).
getItemOffsetFunctionHelper to get an item's virtual offset (VU).
getItemSizeFunctionHelper to get an item's size along scroll axis (VU).
getRowIndexAtFunctionHelper to get the row (or item) index at a vertical virtual offset (VU).
getColIndexAtFunctionHelper to get the column index at a horizontal virtual offset (VU).

useVirtualScrollMasonry

Masonry virtualization driver for a single native scroll container - the engine behind VirtualScrollMasonry. Owns one MasonryLayout frontier chain, renders only the cards intersecting the viewport (plus one segment of overscan per side), derives responsive column geometry from the container width and re-anchors the topmost visible card in content space on every relayout. Headless composable users provide their scrollable element via the hostRef prop.

import { useVirtualScrollMasonry } from '@pdanpdan/virtual-scroll';
import { computed, ref } from 'vue';

const items = ref([...]);
const hostRef = ref<HTMLElement | null>(null);
const props = computed(() => ({
  items: items.value,
  itemHeight: (item, index, width) => item.aspect * width,
  hostRef: hostRef.value
}));

const {
  renderedCards,
  scrollDetails,
  columns,
  columnWidth,
  totalHeight,
  scrollToIndex
} = useVirtualScrollMasonry(props);

Accepts a MaybeRefOrGetter to a VirtualScrollMasonryProps object (the component prop set plus hostRef). Returns renderedCards, scrollDetails, columns, columnWidth, totalHeight, totalHeightExact, scrollToIndex, scrollToOffset, refresh and the reactive internalState (scrollY, viewport size, scrolling flags) - see the component members for the semantics of each member.

useVirtualScrollSizes

Manages the underlying sizing logic using Fenwick Trees. This composable handles prefix sum calculations, size updates, and scroll correction adjustments.

import { useVirtualScrollSizes } from '@pdanpdan/virtual-scroll';
import { computed } from 'vue';

const {
  itemSizesY,
  updateItemSizes,
  getSizeAt
} = useVirtualScrollSizes(computed(() => ({
  props: { items: [], itemSize: 50 },
  isDynamicItemSize: false,
  isDynamicColumnWidth: false,
  defaultSize: 50,
  fixedItemSize: 50,
  direction: 'vertical'
})));

Parameters

Accepts a MaybeRefOrGetter to a UseVirtualScrollSizesProps object.

Return Value

MemberTypeDescription
itemSizesX / YFenwickTreePrefix sum trees for item sizes.
columnSizesFenwickTreePrefix sum tree for column widths.
measuredItemsX / YRef<Uint8Array>Bitmask of measured items.
measuredColumnsRef<Uint8Array>Bitmask of measured columns.
treeUpdateFlagRef<number>Reactive flag that increments when trees update.
sizesInitializedRef<boolean>True after initial sizes are calculated.
getItemBaseSizeFunctionHelper to get item size from props.
getSizeAtFunctionHelper to get current size at index.
initializeSizesFunctionSetup trees from component props.
updateItemSizesFunctionBatch register measurements and trigger corrections.
refreshFunctionReset all measurements and state.

useVirtualScrollbar

Provides the logic for virtual scrollbar interactions. It handles track clicks, thumb dragging, and coordinate mapping (including RTL).

import { useVirtualScrollbar } from '@pdanpdan/virtual-scroll';
import { ref } from 'vue';

const scrollPos = ref(0);

const {
  trackProps,
  thumbProps,
  thumbSizePercent,
  thumbPositionPercent
} = useVirtualScrollbar(() => ({
  axis: 'vertical',
  totalSize: 10000,
  viewportSize: 500,
  position: scrollPos.value,
  scrollToOffset: (val) => { scrollPos.value = val; }
}));

Parameters

Accepts a MaybeRefOrGetter to a UseVirtualScrollbarProps object.

Return Value

MemberTypeDescription
trackPropsComputedRef<object>Attributes and listeners for the track element. Includes class and style.
thumbPropsComputedRef<object>Attributes and listeners for the thumb element. Includes class and style.
viewportPercentComputedRef<number>Viewport size as percentage of total size (0-1).
positionPercentComputedRef<number>Scroll position as percentage of scrollable range (0-1).
thumbSizePercentComputedRef<number>Calculated thumb size (percentage of track, 0-100).
thumbPositionPercentComputedRef<number>Calculated thumb position (percentage of track, 0-100).
isDraggingRef<boolean>Whether the thumb is currently being dragged.

useVirtualScrollInertia

Handles pointer-based scrolling, inertia animation, and mouse wheel events for cases where native scrolling is not available (e.g., massive lists or custom scrollbars).

import { useVirtualScrollInertia } from '@pdanpdan/virtual-scroll';

const {
  isPointerScrolling,
  handlePointerDown,
  handlePointerMove,
  handlePointerUp,
  handleWheel
} = useVirtualScrollInertia({
  useVirtualScrolling: ref(true),
  scrollDetails,
  scrollToOffset: (x, y) => { /* ... */ },
  stopProgrammaticScroll: () => { /* ... */ }
});

Parameters

Accepts an UseVirtualScrollInertiaOptions object.

Return Value

MemberTypeDescription
isPointerScrollingRef<boolean>True when user is actively dragging the content.
handlePointerDown / handlePointerMove / handlePointerUpFunctionPointer event handlers to be bound to the scroll container.
handleWheelFunctionWheel event handler to be bound to the scroll container.
stopInertiaFunctionImmediately stops any active momentum animation.

useVirtualScrollKeyboard

Provides keyboard navigation support for the virtual scroll container, allowing users to navigate using Arrows, Home, End, PageUp, and PageDown keys.

import { useVirtualScrollKeyboard } from '@pdanpdan/virtual-scroll';

const { handleKeyDown } = useVirtualScrollKeyboard({
  props,
  scrollDetails,
  scrollToIndex: (row, col, opt) => { /* ... */ },
  scrollToOffset: (x, y, opt) => { /* ... */ },
  stopProgrammaticScroll: () => { /* ... */ },
  getLoadingSlotSize: () => loadingEl?.offsetHeight ?? 0, // optional
  // ... resolvers
});

Parameters

Accepts an UseVirtualScrollKeyboardOptions object.

  • scrollToOffset: Scrolls to a pixel position. For the End key the composable requests extra range (endExtraX / endExtraY options) so the scroll clamp extends past the virtual content (the loading slot below the items).
  • getLoadingSlotSize (optional): Height of the loading slot. When provided, End includes it in the target so the last item plus the slot fit in the viewport.

Return Value

MemberTypeDescription
handleKeyDownFunctionKeyboard event handler to be bound to the focusable scroll container.

useVirtualScrollObservers

Manages ResizeObserver instances to support fully dynamic item and container sizes.

import { useVirtualScrollObservers } from '@pdanpdan/virtual-scroll';

const { setItemRef } = useVirtualScrollObservers({
  hostRef,
  wrapperRef,
  headerRef,
  footerRef,
  itemRefs,
  updateHostOffset: () => { /* ... */ },
  updateItemSizes: (updates) => { /* ... */ },
  // ...
});

Parameters

Accepts an UseVirtualScrollObserversOptions object.

Return Value

MemberTypeDescription
setItemRefFunctionCallback ref to be used on rendered items to track and measure them.

Extension Reference

useRtlExtension

Automatically detects the text direction (LTR or RTL) of the scroll container and adjusts the coordinate system accordingly. It ensures that horizontal scroll offsets and item positioning are correct in RTL mode.

import { useRtlExtension } from '@pdanpdan/virtual-scroll';

Parameters

This extension does not accept any parameters.

Behavior

  • Injects detection logic into the updateDirection core method.
  • Detects direction from the container element, or falls back to the document root.
  • Automatically flips horizontal item offsets in RTL mode.

useSnappingExtension

Adds scroll snapping behavior to the virtualizer. When user scrolling stops, the extension automatically aligns the viewport to the nearest item based on the snap prop configuration.

import { useSnappingExtension } from '@pdanpdan/virtual-scroll';

Parameters

This extension does not accept any parameters.

Behavior

  • Hooks into onScrollEnd lifecycle event.
  • Calculates the best snap target using resolveSnap utility.
  • Uses scrollToIndex with behavior: 'smooth' to perform the snap.
  • Automatically ignores items larger than the viewport to prevent infinite jumping.

useStickyExtension

Sticky rows and columns. VirtualScroll registers this extension automatically; the sticky behavior itself lives in the core engine, keyed on the stickyIndices, stickyHeader and stickyFooter props - the extension is a pass-through kept for the composable wiring contract.

import { useStickyExtension } from '@pdanpdan/virtual-scroll';

Parameters

This extension does not accept any parameters.

Behavior

  • Sticky items stick below the sticky header (and above the sticky footer): activation and the pushing effect are measured from the sticky start/end offsets (stickyStartX/stickyStartY on StickyParams).
  • Supports both horizontal and vertical stickiness.

useInfiniteLoadingExtension

Simple extension to facilitate infinite scrolling. It monitors the scroll position and triggers a callback when the user reaches a specific distance from the end of the content.

import { useInfiniteLoadingExtension } from '@pdanpdan/virtual-scroll';

const ext = useInfiniteLoadingExtension({
  onLoad: (axis) => {
    console.log(`Load more items on ${axis} axis`);
  }
});

Parameters

PropertyTypeDescription
onLoad(axis: 'vertical' | 'horizontal') => voidCallback triggered when a threshold is met.

Behavior

  • Watches scrollDetails reactively.
  • Respects the loadDistance and loading props from the component.
  • Prevents duplicate triggers while loading is true.
  • Fires as soon as the threshold is reached - including while a programmatic scroll (scrollbar drag, PageDown/End) is still settling - so the loading indicator appears promptly and is not skipped.

usePrependRestorationExtension

Essential for chat-like interfaces. When items are prepended to the beginning of the items array, this extension calculates the added size and applies a scroll correction to maintain the user's perceived position.

import { usePrependRestorationExtension } from '@pdanpdan/virtual-scroll';

Parameters

This extension does not accept any parameters.

Behavior

  • Compares new items with previous items to detect prepended count.
  • Calculates the height (or width) of prepended items.
  • Uses handleScrollCorrection to silently adjust the scroll position before the next frame.

useCoordinateScalingExtension

Enables support for virtually unlimited content sizes. Since browsers have a hard limit on the physical height/width of elements (usually around 10M to 30M pixels), this extension scales the display coordinates so the virtual list can represent billions of pixels.

import { useCoordinateScalingExtension } from '@pdanpdan/virtual-scroll';

Parameters

This extension does not accept any parameters.

Behavior

  • Calculates scaleX and scaleY factors when total size exceeds browser limits.
  • Transparently maps physical scroll events to virtual positions.
  • Automatically disabled when using window as the container (as the browser handles body scrolling differently).

API Reference

Types

ScrollDirection

'vertical' | 'horizontal' | 'both'

Defines the virtualization axes for the VirtualScroll component.

ScrollAxis

'vertical' | 'horizontal'

Used specifically for individual scrollbar instances.

ScrollDetails<T>

PropertyTypeDescription
itemsRenderedItem<T>[]Rendered items in the buffer.
currentIndexnumberFirst visible row index below any sticky header.
currentColIndexnumberFirst visible column index after any sticky column.
currentEndIndexnumberIndex of the last item visible above any sticky footer.
currentEndColIndexnumberIndex of the last column visible before any sticky end column (grid mode).
scrollOffset{ x, y }Current relative scroll position in virtual units (VU).
displayScrollOffset{ x, y }Current physical scroll position in display pixels (DU).
viewportSize{ width, height }Dimensions of the visible viewport in virtual units (VU).
displayViewportSize{ width, height }Physical dimensions of the visible viewport in display pixels (DU).
totalSize{ width, height }Estimated total content dimensions (VU).
isScrollingbooleanActive scrolling state.
isProgrammaticScrollbooleanTrue if triggered by scrollToIndex/Offset.
range{ start, end }Range of currently rendered item indices, including the scroll buffer (inclusive start, exclusive end).
columnRangeColumnRangeVisible column range (grid).

RenderedItem<T>

PropertyTypeDescription
itemTThe source data item.
indexnumberItem's position in the array.
offset{ x, y }Absolute pixel position within the wrapper (DU).
size{ width, height }Current dimensions (VU).
originalX / originalYnumberOffsets before any sticky adjustments (VU).
isStickybooleanIs configured as sticky.
isStickyActivebooleanCurrently stuck to the edge.
isStickyActiveX / isStickyActiveYbooleanCurrently stuck to the horizontal/vertical edge respectively.
stickyOffset{ x, y }Translation applied for sticky pushing effect (DU).

ColumnRange

PropertyTypeDescription
startnumberIndex of first rendered column.
endnumberIndex of last rendered column (exclusive).
padStartnumberPixel space to maintain before columns (VU).
padEndnumberPixel space to maintain after columns (VU).

VirtualScrollProps<T>

Core configuration properties shared between the component and the composables (a subset of the full prop tables above; hostElement is accepted by the composable only).

PropertyTypeDescription
itemsT[]Data source. Required.
itemSizenum | arr | fn | nullSizing logic (fixed, circular array pattern, or function). Default: 40px.
directionScrollDirection'vertical' | 'horizontal' | 'both'.
bufferBefore / bufferAfternumberItems outside viewport. Default: 5.
containerHTMLElement | WindowScroll container. Defaults to component root.
hostElementHTMLElementReference for offset calculation (DU).
ssrRangeSSRRangePre-rendered range for SSR.
columnCountnumberTotal columns for grid mode.
columnWidthnum | arr | fn | nullColumn sizing. Default: 100px.
scrollPaddingStart / Endnum | {x, y}Pixel offsets for scroll limits.
gap / columnGapnumberPixel space between items/cols.
restoreScrollOnPrependbooleanMaintain chat scroll position.
snapSnapModeAuto-alignment after scroll stop.
initialScrollIndexnumberMount-time jump index.
initialScrollAlignScrollAlignment | OptionsAlignment for initial jump.
defaultItemSizenumberEstimate for dynamic items.
defaultColumnWidthnumberEstimate for dynamic columns.
debugbooleanEnable visualization.

StickyParams

Parameters for calculating sticky item offsets (core engine's calculateStickyItem).

PropertyTypeDescription
indexnumberItem index.
isStickybooleanWhether the item is configured as sticky.
directionScrollDirectionScroll direction.
relativeScrollX / relativeScrollYnumberVirtual scroll position (VU).
originalX / originalYnumberVirtual original position of the item (VU).
width / heightnumberVirtual item size (VU).
stickyIndicesnumber[]All configured sticky indices.
fixedSize / fixedWidthnumber | nullFixed item size / column width (VU), null for dynamic.
gap / columnGapnumberItem / column gap (VU).
getItemQueryY / getItemQueryX(index: number) => numberPrefix sum resolvers for offsets (VU).
stickyStartX / stickyStartYnumberSize of sticky start elements (left/top) in DU. Sticky items stick below them; activation and the pushing effect are measured from this offset. Optional, defaults to 0.

UseVirtualScrollbarProps

PropertyTypeDescription
axisScrollAxisAxis of the scrollbar.
totalSizenumberTotal size of content in pixels.
positionnumberCurrent scroll position in pixels.
viewportSizenumberVisible area size in pixels.
scrollToOffset(offset: number) => voidCallback to update position.
containerIdstringID for accessibility.
isRtlbooleanEnable RTL mapping.
ariaLabelstringAccessible label for the scrollbar.

ScrollToIndexOptions

Full configuration for index-based scrolling.

PropertyTypeDescription
alignScrollAlignment | OptionsWhere to align the item (default: 'auto').
behavior'auto' | 'smooth'Scroll behavior (default: 'smooth').

ScrollAlignmentOptions

Allows axis-specific alignment in scrollToIndex.

PropertyTypeDescription
xScrollAlignmentAlignment on the horizontal axis.
yScrollAlignmentAlignment on the vertical axis.

ScrollAlignment

Controls the item's final position in the viewport during scrollToIndex.

ValueBehavior
'start'Aligns to top (vertical) or left (horizontal) edge.
'center'Aligns to viewport center.
'end'Aligns to bottom (vertical) or right (horizontal) edge.
'auto' DefaultSmart: If the item is already fully visible, no scroll occurs. Otherwise, aligns to 'start' or 'end' to bring it into view.

SnapMode

Defines how items align when user scrolling stops. Note: Snapping is disabled for items larger than the viewport.

ValueBehavior
falseNo snapping (default).
true / 'auto'Smart Directional: If scrolling towards start, acts as 'end'. If scrolling towards end, acts as 'start'.
'next'Snaps to the next (closest) snap position in the direction of the scroll.
'start'Snaps the first visible item to the top/left edge if >= 50% is visible, otherwise snaps the next item.
'center'Snaps the item intersecting the viewport center to be exactly centered.
'end'Snaps the last visible item to the bottom/right edge if >= 50% is visible, otherwise snaps the previous item.

UseVirtualScrollSizesProps

PropertyTypeDescription
propsVirtualScrollPropsVirtual scroll configuration.
isDynamicItemSizebooleanWhether items have dynamic heights/widths.
isDynamicColumnWidthbooleanWhether columns have dynamic widths.
defaultSizenumberFallback size for items before they are measured.
fixedItemSizenumber | nullFixed item size if applicable.
directionScrollDirectionThe scroll direction.

FenwickTree

A highly optimized data structure for O(log n) prefix sum calculations and point updates.

MethodSignatureDescription
update(index, delta) => voidUpdate value at index and propagate changes.
query(index) => numberGet prefix sum up to index (exclusive).
get(index) => numberGet individual value at index.
set(index, value) => voidSet the individual value at an index without updating the prefix sum tree.
getValues() => Readonly<Float64Array>Get the underlying values as a read-only view (logical size).
lengthnumberLogical number of items in the tree (read-only property).
findLowerBound(value) => numberFind largest index where prefix sum <= value.
rebuild() => voidRebuild tree from current values in O(n).
resize(size) => voidResize tree while preserving values.
shift(offset) => voidShift values by offset (useful for prepending).

Methods

Detailed reference for the methods exposed on the VirtualScroll component instance (via a template ref) and for the helpers returned by the composables - the badge names the owning API. Methods on the instance are also returned by useVirtualScroll.

Method scrollToIndex()

scrollToIndex(
rowIndex?: number | null,
colIndex?: number | null,
options?: ScrollAlignment | ScrollAlignmentOptions | ScrollToIndexOptions
): ScrollToIndexResult

Ensures a specific item is visible within the viewport. If the item's size is dynamic and not yet measured, the scroll position will be automatically corrected after rendering. Returns the computed scroll targets in virtual and display units (ScrollToIndexResult).

ParameterTypeDescription
rowIndexnumber | nullTarget row. null to keep current Y. Optional.
colIndexnumber | nullTarget column. null to keep current X. Optional.
optionsOptionsAlignment and behavior settings.

Method scrollToOffset()

scrollToOffset(
x?: number | null,
y?: number | null,
options?: { behavior?: 'auto' | 'smooth' } // behavior default: 'auto'
): void

Scrolls the container to an absolute pixel position. Clamped between 0 and the calculated total size; the target is re-clamped when measurements settle (dynamic items).

Method refresh()

Invalidates all cached measurements and triggers a full re-initialization. Use this if your item source data changes in a way that affects sizes without changing the items array reference.

Method updateItemSize()

updateItemSize(
index: number,
inlineSize: number,
blockSize: number,
element?: HTMLElement
): void

Manually registers a new measurement for a single item. The element parameter allows the virtualizer to detect columns from any internal structure using data-col-index attributes.

Method updateItemSizes()

updateItemSizes(updates: Array<{ index: number; inlineSize: number; blockSize: number; element?: HTMLElement }>): void

Batched version of updateItemSize. More efficient when many items are measured simultaneously.

Method updateHostOffset()

Forces a recalculation of the host element's position relative to the scroll container. Call this if the layout changes in a way that shifts the component without triggering a resize event.

Method updateDirection()

Manually triggers the detection of the scroll direction (LTR or RTL). The component also performs this automatically on mount and whenever the container prop changes.

Method getColumnWidth()

getColumnWidth(index: number): number

Returns the currently calculated width for a specific column index, taking measurements and gaps into account.

Method getRowHeight()

getRowHeight(index: number): number

Returns the currently calculated height for a specific row index, taking measurements and gaps into account.

Method getRowOffset()

getRowOffset(index: number): number

Returns the virtual vertical offset (top) of a row in virtual units (VU).

Method getColumnOffset()

getColumnOffset(index: number): number

Returns the virtual horizontal offset (left) of a column in virtual units (VU).

Method getItemOffset()

getItemOffset(index: number): number

Returns the virtual offset of an item along the scroll axis in virtual units (VU).

Method getItemSize()

getItemSize(index: number): number

Returns the size of an item along the scroll axis in virtual units (VU).

Method getRowIndexAt()

getRowIndexAt(offset: number): number

Returns the row (or item) index at a specific vertical (or horizontal in horizontal mode) virtual offset (VU).

Method getColIndexAt()

getColIndexAt(offset: number): number

Returns the column index at a specific horizontal virtual offset (VU).

Method getItemAriaProps()

getItemAriaProps(index: number): Record<string, string | number | undefined>

Returns the ARIA attributes for an item at the given index. Includes role, aria-setsize, and aria-posinset (or aria-rowindex for grids).

Method getCellAriaProps()

getCellAriaProps(colIndex: number): Record<string, string | number | undefined>

Returns the ARIA attributes for a cell at the given column index. Only relevant for direction="both" or role="grid". Includes role="gridcell" and aria-colindex.

Method stopProgrammaticScroll()

Immediately halts any active smooth scroll animation and clears pending scroll requests.

useVirtualScroll handleScrollCorrection()

handleScrollCorrection(addedX: number, addedY: number): void

Applies the delta accumulated by measurement changes above the viewport, keeping the visible content stable when item sizes settle.

useVirtualScrollSizes getItemBaseSize()

getItemBaseSize(item: T, index: number): number

Returns the configured base size for an item (itemSize function result or the default size) used before measurement.

useVirtualScrollSizes getSizeAt()

getSizeAt(index: number, sizeProp, defaultSize: number, gap: number, tree: FenwickTree, isX: boolean): number

Queries the size of an index from a Fenwick tree, honoring the configured size source, defaults, gaps and tree updates.

useVirtualScrollSizes initializeSizes()

initializeSizes(): void

Rebuilds the size trees from the configured sizes and clears all measurement flags.

useVirtualScrollInertia handlePointerDown()

handlePointerDown(event: PointerEvent): void

Starts scaled drag/inertia handling on pointer down.

useVirtualScrollInertia handlePointerMove()

handlePointerMove(event: PointerEvent): void

Tracks pointer movement while dragging (used by scaled touch/wheel inertia).

useVirtualScrollInertia handlePointerUp()

handlePointerUp(event: PointerEvent): void

Ends a drag sequence and launches inertia when needed.

useVirtualScrollInertia handleWheel()

handleWheel(event: WheelEvent): void

Handles wheel input when coordinate scaling is active so 1:1 movement is preserved.

useVirtualScrollInertia stopInertia()

stopInertia(): void

Immediately halts any running inertia animation.

useVirtualScrollKeyboard handleKeyDown()

handleKeyDown(event: KeyboardEvent): void

Implements keyboard navigation (arrows, Home/End, PageUp/PageDown) with alignment support.

useVirtualScrollObservers setItemRef()

setItemRef(el: unknown, index: number): void

Callback ref used by rendered items: registers/unregisters elements for dynamic measurement.

Utility Functions

Type Guards

isElement(val?): Checks if a value is a standard HTMLElement (explicitly excluding window). Optional.

isWindow(val?): Checks for global window object. Optional.

isBody(val?): Checks for document.body. Optional.

isWindowLike(val?): Matches window or body. Optional.

isScrollableElement(val?): Checks if a value is an HTMLElement that exposes native scroll properties like scrollLeft. Optional.

isScrollToIndexOptions(val): Type guard for ScrollToIndexOptions object.

getPaddingX / getPaddingY

(p?: number | { x?: number; y?: number } | null, direction?: ScrollDirection): number

Resolves a scroll-padding value for the target axis: a number applies along the scroll axis, an object supplies per-axis x/y.

Coordinate Mapping

displayToVirtual(displayPos, hostOffset, scale): Maps display pixels (DU) to virtual content position (VU).

virtualToDisplay(virtualPos, hostOffset, scale): Maps virtual content position (VU) to display pixels (DU).

isItemVisible

(itemPos, itemSize, scrollPos, viewSize, stickyStart?, stickyEnd?): boolean

Highly accurate visibility check (VU) used for auto-alignment and rendering ranges.

Default Values & Constants

DEFAULT_ITEM_SIZE40px
DEFAULT_COLUMN_WIDTH100px
DEFAULT_BUFFER5 items
DEFAULT_MASONRY_TARGET_COLUMN_WIDTH240px
DEFAULT_MASONRY_MIN_COLUMNS1
DEFAULT_MASONRY_MAX_COLUMNS10
DEFAULT_MASONRY_GAP10px
DEFAULT_MASONRY_SEGMENT_SIZE500 items
BROWSER_MAX_SIZE10,000,000px

Values applied when props are omitted or dynamic estimates are needed. BROWSER_MAX_SIZE defines the scaling threshold.

SSR & Hydration

The library supports Server-Side Rendering via the ssrRange prop. When provided, the specified items are rendered "in-flow" on the server.

Hydration is automatic: the client renders the same in-flow items before mounting to match the server HTML, then scrolls to the pre-rendered range and switches to absolute positioning for virtualization.