React
Virtualising a long list
Virtualising a list means mounting only the visible rows: the DOM stays small, but height, accessibility and search need handling.
Key points
- Virtualisation mounts only the visible rows plus a margin, not the whole list.
- TanStack Virtual and react-window ship under the MIT licence and need a container with a known height.
- Browser find and printing break a virtualised list: both need explicit fallbacks.
Virtualising a long list means mounting only the visible rows, plus a small margin, instead of all of them: the DOM stops growing with the number of items.
The problem, in numbers
Ten thousand rows at five nodes each produce fifty thousand elements in the DOM. The browser lays all of them out, even off screen: the list opens in seconds and every filter re-measures them. Virtualisation brings the count down to a few dozen rows, whatever the total.
How it is used
import { useRef } from "react";
import { useVirtualizer } from "@tanstack/react-virtual";
export function Rows({ items }) {
const parentRef = useRef(null);
const virtual = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 36,
});
return (
{virtual.getVirtualItems().map((row) => (
{items[row.index].name}
))}
);
}
The container has a fixed height and overflow: auto; the hook returns only the rows inside the window, each one placed with translateY at its own offset. The list looks complete because the inner container keeps the full height.
What you pay
- Row height: variable content needs a real measurement, or the scroll jumps.
- Browser find: text that is not mounted cannot be found with Ctrl+F, and that breaks a common habit.
- Accessibility: a list of a few dozen nodes does not announce the total; it needs
aria-rowcountand a text summary.
Installation
npm i @tanstack/react-virtual
npm run dev
TanStack Virtual ships under the MIT licence. react-window, also MIT, covers the same case with a tighter API and fewer dependencies.
Live records in the catalogue
Three records show dense lists and tables: leads-data-table, lumina-interactive-list and rolling-list. To browse by category start from 111,985 components.
The limitation
Virtualisation does not make a slow row fast: if every item mounts a nested table, the problem stays. It does not fix printing or export either, which need every row: generate that output outside the visible DOM.
Where to continue
The measured comparison of tables is in React table libraries; key mistakes in lists are in key warnings in lists; the ecosystem comparison is in React libraries 2026.
Frequently asked questions
How many rows justify virtualisation?
Above a thousand rows, usually. Below three hundred the cost of measuring and scrolling does not repay the complexity.
Does virtualisation break Ctrl+F?
Yes, for rows that are not mounted. Many lists add an internal search that filters the data, so the browser feature is not needed.
Does it work with rows of different heights?
Yes, with dynamic measurement: TanStack Virtual offers it, but each unmounted row is estimated and the scroll may correct itself.
Do I need a library or a few lines of code?
A simple vertical list needs a few lines; grids, variable rows and tables are better served by a library that handles measurement.
Is a virtualised list accessible to screen readers?
Not on its own. The total must be declared with ARIA attributes and a summary, or the list sounds short.