/ Docs

Getting Started #

PublishedUpdated May 27, 2026

vlist v2 is a high-performance virtual scrolling library with zero dependencies and a plugin architecture.

Install #

# npm
npm install vlist

# bun
bun add vlist

# pnpm
pnpm add vlist

Import styles:

import "vlist/styles";

Or via link tag:

<link rel="stylesheet" href="node_modules/vlist/dist/vlist.css" />

Basic Usage #

import { createVList } from "vlist";
import "vlist/styles";

const list = createVList({
  container: "#my-list",
  item: {
    height: 48,
    template: (item) => `<div class="my-item">${item.name}</div>`,
  },
  items: [
    { id: 1, name: "Item 1" },
    { id: 2, name: "Item 2" },
    // ...
  ],
});

Configuration #

Option Type Default Description
container HTMLElement | string required DOM element or CSS selector
item.height number | (index) => number Fixed or per-index height (Mode A)
item.estimatedHeight number Auto-measure with ResizeObserver (Mode B)
item.template (item, index, state) => string | HTMLElement required Render function
item.gap number 0 Spacing between items
item.striped boolean | "data" | "even" | "odd" false Zebra stripe classes
items T[] [] Initial data
overscan number 3 Extra items rendered offscreen
orientation "vertical" | "horizontal" "vertical" Scroll axis
padding number | [top, bottom] | [t, r, b, l] 0 Container padding
classPrefix string "vlist" CSS class prefix
reverse boolean false Reverse scroll direction
ariaLabel string Container aria-label
scroll.idleTimeout number 150 Idle detection timeout (ms)

Sizing Modes #

Mode A — fixed height: Pass item.height as a number or a function. Position math is O(1) — use this when possible.

// Uniform height
item: { height: 48, template: renderItem }

// Per-index height
item: { height: (i) => sizes[i], template: renderItem }

Mode B — auto-measure: Pass item.estimatedHeight instead. vlist uses ResizeObserver to measure each item once after first render. Use when item height varies by content.

const list = createVList({
  container: "#my-list",
  item: {
    estimatedHeight: 60,
    template: (item) => `<div class="my-item">${item.body}</div>`,
  },
  items: data,
});

Item Template #

The template function receives (item, index, state). state carries selected and focused booleans (populated by the selection plugin when active).

// Return an HTML string
template: (item, index, state) =>
  `<div class="row${state.selected ? " row--selected" : ""}">${item.name}</div>`

// Return an HTMLElement
template: (item, index, state) => {
  const el = document.createElement("div");
  el.className = "row";
  if (state.selected) el.classList.add("row--selected");
  el.textContent = item.name;
  return el;
}

Methods #

Method Description
setItems(items) Replace the full dataset
appendItems(items) Add items to the end
prependItems(items) Add items to the beginning
insertItem(item, index?) Insert at position (appends if omitted)
updateItem(id, updates) Partial update by ID
removeItem(id) Remove a single item by ID
removeItems(ids) Bulk remove by IDs
getItemAt(index) Get item at index
getIndexById(id) Get index by ID
scrollToIndex(index, align?) Scroll to item ("start", "center", "end")
getScrollPosition() Current scroll offset in pixels
on(event, handler) Subscribe to an event — returns an unsubscribe function
off(event, handler) Unsubscribe a specific handler
destroy() Teardown — removes DOM, listeners, and observers

Events #

const unsub = list.on("item:click", ({ item, index, event }) => {
  console.log("Clicked", item.id);
});

// Stop listening
unsub();
Event Payload
item:click { item, index, event }
item:dblclick { item, index, event }
item:contextmenu { item, index, event }
scroll { scrollPosition, direction }
scroll:idle { scrollPosition }
range:change { range: { start, end } }
resize { width, height }
data:change { type, id }
destroy

Plugins add their own events (selection:change, sort:start, etc.) — see the plugin docs.

Plugins #

Pass plugins as a second argument to createVList. They are tree-shakeable — only imported plugins are included in your bundle.

import { createVList, grid, selection } from "vlist";

const list = createVList({
  container: "#app",
  item: { height: 80, template: renderItem },
  items: data,
}, [
  grid({ columns: 3, gap: 8 }),
  selection({ mode: "multiple" }),
]);

See plugins/overview.md for the full list of available plugins and their options.

Cleanup #

list.destroy();

Call destroy() when removing the list from the DOM — it clears all event listeners, ResizeObservers, and DOM nodes created by vlist.