/ Docs

API Reference #

createVList(config, plugins?) #

Creates a virtual list instance.

import { createVList } from "vlist";

const list = createVList(config, [plugin1(), plugin2()]);

Config #

Option Type Default Description
container HTMLElement | string required DOM element or CSS selector
item.height number | (index, context?) => number Fixed or per-index item height
item.width number | (index) => number Fixed or per-index item width (horizontal mode)
item.estimatedHeight number Auto-measure via ResizeObserver (Mode B)
item.estimatedWidth number Auto-measure for horizontal (Mode B)
item.template (item, index, state) => string | HTMLElement required Render function
item.gap number 0 Spacing between items (px)
item.striped boolean | "data" | "even" | "odd" false Zebra stripe classes
items T[] [] Initial dataset
overscan number 3 Extra items rendered offscreen
orientation "vertical" | "horizontal" "vertical" Scroll axis
padding number | [number, number] | [t, r, b, l] 0 Container padding (px)
classPrefix string "vlist" CSS class prefix
interactive boolean true Enable click/keyboard handling
reverse boolean false Reverse scroll direction
ariaLabel string Container aria-label
scroll.wheel boolean true Mouse wheel scrolling
scroll.gutter "auto" | "stable" "auto" Scrollbar space reservation
scroll.idleTimeout number 150 Idle detection timeout (ms)

Instance Properties #

Property Type Description
element HTMLElement Root DOM container
items readonly T[] Current item array
total number Total item count (includes virtual items from plugins)

Instance Methods #

Data #

Method Signature Description
setItems (items: T[]) => void Replace entire dataset
appendItems (items: T[]) => void Add items to end
prependItems (items: T[]) => void Add items to beginning
insertItem (item: T, index?: number) => void Insert at position (default: end)
updateItem (id: string | number, updates: Partial<T>) => void Partial update by ID
removeItem (id: string | number) => void Remove by ID
removeItems (ids: ReadonlyArray<string | number>) => number Bulk remove, returns count
getItemAt (index: number) => T | undefined Get item at index
getIndexById (id: string | number) => number Get index by ID (-1 if not found)

Scroll #

Method Signature Description
scrollToIndex (index: number, align?) => void Scroll to item
getScrollPosition () => number Current scroll offset (px)

scrollToIndex accepts a string alignment or an options object:

// Simple alignment
list.scrollToIndex(50, "center");

// Smooth scroll
list.scrollToIndex(50, { align: "end", behavior: "smooth", duration: 300 });

// Custom easing
list.scrollToIndex(50, {
  align: "start",
  behavior: "smooth",
  duration: 800,
  easing: (t) => 1 - Math.pow(1 - t, 3), // ease-out cubic
});

// Elastic easing (overshoot + settle)
const elasticOut = (t) => {
  if (t === 0 || t === 1) return t;
  return Math.pow(2, -10 * t) * Math.sin((t - 0.075) * (2 * Math.PI) / 0.3) + 1;
};
list.scrollToIndex(999, { behavior: "smooth", duration: 800, easing: elasticOut });

When easing is omitted, a default easeInOutQuad curve is used.

Events #

Method Signature Description
on (event, handler) => Unsubscribe Subscribe (returns unsubscribe fn)
off (event, handler) => void Unsubscribe

Lifecycle #

Method Signature Description
destroy () => void Tear down instance, remove DOM, clean up listeners

Events #

Core #

Event Payload Description
item:click { item, index, event: MouseEvent } Item clicked
item:dblclick { item, index, event: MouseEvent } Item double-clicked
item:contextmenu { item, index, event: MouseEvent } Item right-clicked
scroll { scrollPosition, direction: "up" | "down" } Scroll position changed
scroll:idle { scrollPosition } Scrolling stopped
velocity:change { velocity, reliable } Scroll velocity changed
range:change { range: { start, end } } Visible range changed
resize { width, height } Container resized
data:change { type: "insert" | "add" | "remove" | "update", id } Data mutated
error { error: Error, context: string, viewport?: ErrorViewportSnapshot } Error occurred
destroy Instance destroyed

Selection Plugin #

Event Payload
selection:change { selected: (string | number)[], items: T[] }
focus:change { id: string | number, index: number }
delete { selected: (string | number)[], items: T[] }

Async Plugin #

Event Payload
load:start { offset, limit }
load:end { items, total? }

Transition Plugin #

Event Payload
remove:end { id: string | number }

Table Plugin #

Event Payload
column:resize { key, index, previousWidth, width }
column:sort { key, index, direction: "asc" | "desc" | null }

Sortable Plugin #

Event Payload
sort:start { index }
sort:move { fromIndex, currentIndex }
sort:end { fromIndex, toIndex }
sort:cancel { originalItems }

Types #

VListItem #

interface VListItem {
  id: string | number;
  [key: string]: unknown;
}

ItemTemplate #

type ItemTemplate<T> = (item: T, index: number, state: ItemState) => string | HTMLElement;

interface ItemState {
  selected: boolean;
  focused: boolean;
}

ScrollToOptions #

interface ScrollToOptions {
  align?: "start" | "center" | "end";
  behavior?: "auto" | "smooth";
  duration?: number;
  easing?: (t: number) => number;
}

The easing function receives a normalized time t (0–1) and returns a normalized progress value. The default is easeInOutQuad. Common easing functions:

Easing Function
Linear t => t
Ease-out cubic t => 1 - Math.pow(1 - t, 3)
Ease-in-out quad t => t < 0.5 ? 2*t*t : 1 - (-2*t+2)**2/2 (default)
Elastic out t => Math.pow(2,-10*t) * Math.sin((t-0.075)*2*Math.PI/0.3) + 1

ScrollSnapshot #

interface ScrollSnapshot {
  index: number;
  offsetInItem: number;
  total?: number;
  dataIndex?: number;
  dataTotal?: number;
  offsetRatio?: number;
  selectedIds?: (string | number)[];
  focusedId?: string | number;
  scrollTop?: number;
  scrollRatio?: number;
}

VListAdapter (async plugin) #

interface VListAdapter<T> {
  read(params: AdapterParams): Promise<AdapterResponse<T>>;
}

interface AdapterParams {
  offset: number;
  limit: number;
  cursor: string | undefined;
  signal: AbortSignal;
}

interface AdapterResponse<T> {
  items: T[];
  total?: number;
  cursor?: string;
  hasMore?: boolean;
}

ScrollbarOptions #

interface ScrollbarOptions {
  autoHide?: boolean;
  autoHideDelay?: number;
  minThumbSize?: number;
  showOnHover?: boolean;
  hoverZoneWidth?: number;
  showOnViewportEnter?: boolean;
}

VListPlugin (custom plugins) #

interface VListPlugin<T extends VListItem = VListItem> {
  readonly name: string;
  readonly priority?: number;
  readonly conflicts?: readonly string[];

  setup?(ctx: PluginContext<T>): void;

  hooks?: {
    onCalculate?(state: EngineState): void;
    onCommit?(state: EngineState): void;
    onAfterScroll?(scrollPosition: number, direction: number): void;
    onIdle?(): void;
    onResize?(width: number, height: number): void;
  };

  destroy?(): void;
}