/ Docs

VList API Reference #

Complete reference for the core VList API — factory, configuration, properties, and methods.

For feature-specific documentation (withGrid, withAsync, withSelection, etc.), see the Features section.


Installation #

npm install @floor/vlist
import { vlist } from '@floor/vlist'
import '@floor/vlist/styles'

Optional extras (variants, loading states, animations):

import '@floor/vlist/styles/extras'

Quick Start #

import { vlist } from '@floor/vlist'
import '@floor/vlist/styles'

const list = vlist({
  container: '#my-list',
  items: Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i}` })),
  item: {
    height: 48,
    template: (item) => `<div>${item.name}</div>`,
  },
}).build()

list.scrollToIndex(5000)
list.on('item:click', ({ item }) => console.log(item))

vlist(config) #

Creates a VListBuilder — a chainable object for composing features before materializing the list.

function vlist<T extends VListItem>(config: BuilderConfig<T>): VListBuilder<T>

Returns a VListBuilder with two methods:

Method Description
.use(feature) Register a feature. Chainable.
.build() Materialize the list — creates DOM, initializes features, returns the instance API.

Example with features:

import { vlist, withSelection, withScrollbar, withSnapshots } from '@floor/vlist'

const list = vlist({
  container: '#app',
  items: data,
  item: { height: 56, template: renderRow },
})
  .use(withSelection({ mode: 'multiple' }))
  .use(withScrollbar())
  .use(withSnapshots())
  .build()

Configuration #

All configuration types passed to vlist() and its features.

VListItem #

All items must have a unique id. This is the only constraint — add any other fields your template needs.

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

BuilderConfig #

The top-level configuration object passed to vlist().

interface BuilderConfig<T extends VListItem = VListItem> {
  container:    HTMLElement | string
  item:         ItemConfig<T>
  items?:       T[]
  overscan?:    number
  orientation?: 'vertical' | 'horizontal'
  reverse?:     boolean
  classPrefix?: string
  ariaLabel?:   string
  scroll?:      ScrollConfig
}
Property Type Default Description
container HTMLElement | string Required. The container element or a CSS selector.
item ItemConfig Required. Item sizing and template.
items T[] [] Static items array. Omit when using withAsync.
overscan number 3 Extra items rendered outside the viewport in each direction. Higher values reduce blank flashes during fast scrolling at the cost of more DOM nodes.
orientation 'vertical' | 'horizontal' 'vertical' Scroll axis. Use 'horizontal' for carousels or timelines.
reverse boolean false Reverse mode — list starts scrolled to the bottom. appendItems auto-scrolls if already at bottom; prependItems preserves scroll position. Useful for any bottom-anchored content: chat, logs, activity feeds, timelines.
classPrefix string 'vlist' CSS class prefix for all internal elements.
ariaLabel string Sets aria-label on the root listbox element.
scroll ScrollConfig Fine-grained scroll behavior options.

ItemConfig #

Controls how items are sized and rendered. Supports two sizing strategies, each with an axis-neutral SizeCache underneath — the same code path handles vertical heights and horizontal widths.

interface ItemConfig<T extends VListItem = VListItem> {
  height?:          number | ((index: number, context?: GridHeightContext) => number)
  width?:           number | ((index: number) => number)
  estimatedHeight?: number
  estimatedWidth?:  number
  template:         ItemTemplate<T>
}
Property Type Default Description
height number | (index, ctx?) => number Item size in pixels along the main axis. Required for vertical lists. A plain number enables the fast path (zero per-item overhead). A function enables variable sizes with a prefix-sum array for O(1) offset lookup and O(log n) index search. In grid mode, receives a context object as a second argument — see Grid.
width number | (index) => number Item size in pixels along the main axis for horizontal lists (orientation: 'horizontal'). Same semantics as height. Ignored in vertical mode.
estimatedHeight number Estimated size for auto-measurement (Mode B). Items are rendered at this size, then measured via ResizeObserver, and the real size is cached. Use for content whose size can't be predicted from data. Ignored if height is also set.
estimatedWidth number Horizontal equivalent of estimatedHeight. Ignored if width is also set.
template ItemTemplate<T> Required. Render function for each visible item.

Sizing modes #

vlist supports two sizing strategies. Pick the one that matches your data:

Mode A — Known sizes. Use when you can derive the item size from data alone, without rendering. This is the fast path — zero measurement overhead.

Variant When to use Example use cases
Fixed (number) All items have the same size Contact lists, data tables, settings panels
Variable (function) Size varies but is computable from data Expanded/collapsed rows, mixed row types (header vs item)
// Fixed — all items 48px
item: {
  height: 48,
  template: (item) => `<div>${item.name}</div>`,
}

// Variable — derive size from data
item: {
  height: (index) => data[index].type === 'header' ? 64 : 48,
  template: (item) => `<div>${item.name}</div>`,
}

Mode B — Auto-measurement. Use when the size depends on rendered content that you can't predict from data — variable-length user text, images with unknown aspect ratios, mixed-media feeds. You provide an estimate; vlist renders items at that size, measures the actual DOM size via ResizeObserver, caches the result, and adjusts scroll position to prevent visual jumps.

// Social feed — posts vary from one-liner to multi-paragraph with images
item: {
  estimatedHeight: 120,
  template: (post) => `
    <article class="post">
      <div class="post__body">${post.text}</div>
      ${post.image ? `<img src="${post.image}" />` : ''}
    </article>
  `,
}

Once an item is measured, it behaves identically to Mode A — subsequent renders use the cached size with no further measurement. See Measurement for the full architecture.

Precedence: If both height and estimatedHeight are set, height wins (Mode A). The estimate is silently ignored. This means upgrading from Mode B to Mode A is a single config change.

Scaling: All three variants (fixed, variable, measured) work with withScale for 1M+ items. The compression ratio is computed from the actual total size reported by the SizeCache, not from a uniform item size assumption — so variable and measured sizes compress correctly.


ItemTemplate #

type ItemTemplate<T = VListItem> = (
  item:  T,
  index: number,
  state: ItemState,
) => string | HTMLElement
Parameter Type Description
item T The data item for this row.
index number The item's position in the full list.
state ItemState Rendering state flags.
interface ItemState {
  selected: boolean   // true when this item is in the selection set
  focused:  boolean   // true when this item has keyboard focus
}

ScrollConfig #

Scroll behavior options, passed as the scroll property of BuilderConfig.

interface ScrollConfig {
  wheel?:       boolean
  wrap?:        boolean
  idleTimeout?: number
  element?:     Window
  scrollbar?:   'native' | 'none' | ScrollbarOptions
}
Property Type Default Description
wheel boolean true Whether mouse-wheel scrolling is enabled. Set to false for wizard-style navigation.
wrap boolean false Circular scrolling — scrollToIndex past the last item wraps to the beginning, and vice versa. Useful for carousels.
idleTimeout number 150 Milliseconds after the last scroll event before the list is considered idle. Used by async loading and velocity tracking.
element Window Use the browser window as the scroll container (document-level scrolling). Assign window.
scrollbar 'native' | 'none' | ScrollbarOptions custom Scrollbar mode. Omit for the default custom scrollbar. 'native' shows the browser's native bar. 'none' hides all scrollbars. A ScrollbarOptions object fine-tunes the custom scrollbar.

ScrollbarOptions #

Fine-tuning for the custom scrollbar. Pass as scroll.scrollbar or to withScrollbar().

interface ScrollbarOptions {
  autoHide?:             boolean
  autoHideDelay?:        number
  minThumbSize?:         number
  showOnHover?:          boolean
  hoverZoneWidth?:       number
  showOnViewportEnter?:  boolean
}
Property Type Default Description
autoHide boolean true Hide the scrollbar thumb after the list goes idle.
autoHideDelay number 1000 Milliseconds of idle time before the thumb fades out.
minThumbSize number 30 Minimum thumb size in pixels. Prevents the thumb from becoming too small to grab.
showOnHover boolean true Reveal the scrollbar when the cursor moves near the scrollbar edge.
hoverZoneWidth number 16 Width in pixels of the invisible hover detection zone along the scrollbar edge.
showOnViewportEnter boolean true Show the scrollbar whenever the cursor enters the list viewport.

Properties #

The object returned by .build() exposes these read-only properties.

element #

readonly element: HTMLElement

The root DOM element created by vlist. Already inserted into the container you specified — no need to append it yourself.

items #

readonly items: readonly T[]

The current items array. This is a read-only snapshot — mutating it has no effect. Use setItems, appendItems, prependItems, updateItem, or removeItem to change data.

total #

readonly total: number

Total item count. For static lists this equals items.length. With withAsync, this reflects the total reported by the adapter (which may be larger than the number of items currently loaded).


Methods #

All methods are available on the object returned by .build().

setItems #

Replace the entire dataset. Triggers a full re-render.

setItems(items: T[]): void
list.setItems(newData)

appendItems #

Add items to the end of the list. In reverse mode, auto-scrolls to the bottom if the user was already there.

appendItems(items: T[]): void
list.appendItems([{ id: 101, name: 'New item' }])

prependItems #

Add items to the beginning of the list. Preserves the current scroll position so older content loads silently above.

prependItems(items: T[]): void

updateItem #

Patch a single item by ID. Only the provided fields are merged; the item's position in the list is unchanged.

updateItem(id: string | number, updates: Partial<T>): void
list.updateItem(42, { name: 'Renamed', unread: false })

removeItem #

Remove a single item by ID.

removeItem(id: string | number): void

reload #

Clear all loaded data and re-fetch from the beginning. When used with withAsync, triggers a fresh adapter call. Returns a promise that resolves when the initial page is loaded.

reload(): Promise<void>

scrollToIndex #

Scroll so that the item at index is visible.

scrollToIndex(
  index: number,
  alignOrOptions?: 'start' | 'center' | 'end' | ScrollToOptions
): void

Accepts either a simple alignment string or a full options object:

interface ScrollToOptions {
  align?:    'start' | 'center' | 'end'   // default: 'start'
  behavior?: 'auto' | 'smooth'            // default: 'auto' (instant)
  duration?: number                        // default: 300 (ms, smooth only)
}
list.scrollToIndex(500)
list.scrollToIndex(500, 'center')
list.scrollToIndex(500, { align: 'center', behavior: 'smooth', duration: 400 })

When scroll.wrap is true, indices past the last item wrap to the beginning and negative indices wrap from the end.

Note: There is no scrollToItem(id) method. If you need to scroll to an item by ID, maintain your own id → index map and call scrollToIndex.


cancelScroll #

Immediately stop any in-progress smooth scroll animation.

cancelScroll(): void

getScrollPosition #

Returns the current scroll offset in pixels along the main axis.

getScrollPosition(): number

on #

Subscribe to an event. Returns an unsubscribe function.

on<K extends keyof VListEvents<T>>(
  event: K,
  handler: (payload: VListEvents<T>[K]) => void
): Unsubscribe
const unsub = list.on('item:click', ({ item, index }) => {
  console.log('clicked', item)
})

// Later
unsub()

See Events for all event types and payloads.

off #

Unsubscribe a previously registered handler by reference.

off<K extends keyof VListEvents<T>>(
  event: K,
  handler: Function
): void
const handler = ({ item }) => console.log(item)
list.on('item:click', handler)
list.off('item:click', handler)

destroy #

Tear down the list — removes DOM elements and event listeners, disconnects observers, cancels pending requests, and clears all internal references. Always call this when removing the list from the page.

destroy(): void
// React example
useEffect(() => {
  const list = vlist({ ... }).build()
  return () => list.destroy()
}, [])

See Also #

  • Events — All event types, payloads, and subscription patterns
  • Types — Full TypeScript type reference
  • Constants — Default values and thresholds
  • Exports — Low-level utilities and feature authoring
  • Features — All features with examples, bundle costs, and compatibility

VList is built and maintained by Floor IO. Source at github.com/floor/vlist.