Overview
Fixed-height windowing primitive for long append-only lists. VirtualList owns a
native vertical scroll container and renders only the visible rows plus overscan;
you own the row markup through the row snippet.
Version 1 requires a fixed itemHeight in pixels. Variable or measured row
heights are intentionally out of scope for this primitive.
Usage
Use stickToBottom for live log tails: appending while the user is already at
the bottom keeps the newest row in view, while appending with the viewport
scrolled up leaves the scroll position unchanged.
When to use
- Rendering thousands of same-height append-only rows such as logs, event streams, or activity feeds.
- You need a reusable primitive that owns native vertical scrolling but leaves row markup to a snippet.
- Rows have substantially variable heights that must be measured dynamically — v1 requires a fixed itemHeight.
- Rendering columns or two-dimensional grids — use data-grid for grid semantics and column virtualization.
Examples
10,000 item virtual list
A native scroll container rendering only the visible slice of a 10,000 item append-only event stream.
Props
| Name | Type | Default | Description |
|---|---|---|---|
items required | Item[] | Items in full logical order. Only the visible window is mounted. | |
itemHeight required | number | Fixed row height in pixels. Variable and measured row heights are out of scope for v1; pass the known or estimated fixed height for every row. | |
overscan | number | 5 | Extra rows rendered before and after the visible window. Defaults to 5. |
height | text | '20rem' | CSS block-size for the native scroll container.
Defaults to "20rem". |
stickToBottom | boolean | false | When true, appending items while the viewport is already at the bottom keeps the newest item pinned in view. Appending while scrolled up leaves the scroll position unchanged. |
tabindex | number | 0 | Override the default focus behavior. The component sets tabindex="0"
by default so keyboard users can reach the native scroll container for
arrow-key scrolling. Pass tabindex={-1} when the viewport should be
programmatically focusable without entering the tab order. |
getKey | (item: Item, index: number) => VirtualListKey | Stable key extractor. Omit only when items are append-only and never reordered; the component will fall back to full-array indexes. | |
row required | snippet | Rendered row snippet. Receives the item and its virtual row context. | |
role | AriaRole | undefined | null | 'list' | |
onscroll | UIEventHandler<T> | undefined | null |