ValyroUI Русская версия

Virtual List

Virtual list for 100,000 rows. Free, MIT · Data and metrics › Tables

A windowed list: renders only what is on screen, so 100k rows scroll smoothly. Fixed or measured item sizes, optional listbox keyboard model and selection, sticky section headers, and infinite loading at either end (with a stable scroll position when older items are prepended).

Installation

npx valyro-cli@latest add virtual-list
npx shadcn@latest add https://valyro.xyz/r/virtual-list.json
npm install valyro-ui

import 'valyro-ui/styles.css';
import { VirtualList } from 'valyro-ui';

Props

PropTypeDefault
items *readonly T[]All items; only those near the viewport are rendered.
renderItem *(item: T, index: number, state: VirtualListItemState) => ReactNodeRenders one item's content; state carries its active, selected and disabled flags in listbox mode.
getItemKey(item: T, index: number) => KeydefaultItemKeyStable key per item, used for selection and to keep measured sizes. Defaults to item.id, then the index.
labelstringAccessible name of the list.
heightCSSProperties['height']400Height of the scrolling viewport.
itemSizenumberEvery item has this height in px: nothing is measured, which is the fastest mode (100k+ rows).
estimatedItemSizenumber48Starting guess for measured items (when itemSize is not set).
gapnumber0Space between items, in px.
paddingnumber0Space before the first and after the last item, in px.
overscannumber6Items rendered beyond each edge of the viewport.
selectionMode'none' | 'single' | 'multiple''none'single / multiple turn the list into a listbox with keyboard navigation (arrows, Home/End, Page Up/Down, type-ahead with getItemText) and selection. none with onAction is a navigable listbox without selection; otherwise a plain list.
selectedKey[]Controlled selected; use with onSelectedChange.
defaultSelectedKey[][]Initial selected when uncontrolled.
onSelectedChange(keys: Key[]) => voidCalled with the new selected when it changes.
onAction(item: T, index: number) => voidCalled on Enter or click.
getItemText(item: T) => stringText for type-ahead (listbox mode).
isItemDisabled(item: T, index: number) => booleanItems skipped by keyboard navigation, type-ahead, clicks and selection (listbox mode).
getSection(item: T, index: number) => stringSection of an item; consecutive items with the same section get a header. Items must already be in section order.
renderSectionHeader(section: string) => ReactNodeContent of a section header. Defaults to the section name.
stickySectionHeadersbooleantrueKeeps the current section's header pinned to the top.
sectionHeaderSizenumber32Header height in px when itemSize is set (measured otherwise).
onEndReached() => voidCalled once per item count when the end is within threshold px, to load the next page.
onStartReached() => voidCalled once per item count when the start is within threshold px, to load older items (chat).
thresholdnumber240Distance from an end, in px, that counts as reaching it.
loadingboolean | 'start' | 'end'falseShows a loading row at the end (or at the start while loading older items with onStartReached).
followOutputbooleanfalseStarts at the last item and keeps following new ones while scrolled to the end — for chat and logs.
variant'panel' | 'plain''panel'panel frames the viewport like a card; plain leaves it bare for use inside other surfaces.
dividersbooleanfalseHairlines between items.
emptyReactNodeReplaces the default empty state.
labelsPartial<VirtualListLabels>Overrides for the built-in English text; keys left out keep their defaults.

Related components

Previous: Data Grid · Next: Bulk Action Bar · All Data and metrics