Scrolling Performance
What a scroll costs, and what keeps it smooth once your features are turned on. Sort, resize, reorder and select in the ten-thousand-row grid below — the number of rendered rows stays bounded wherever you are in the data.
Ten thousand rows with everything on
Sorting, column resize, column reorder and multi-row selection, all live on the scrolling grid rather than beside it. Scroll to the very bottom — the counter settles at a few dozen rows and stays there.
0 of 10,000 rows are in the DOM right now.
A row that leaves and comes back is the same row
Select a few rows, then scroll far enough that they are gone from the page entirely and scroll back. They are still selected — the grid holds selection against the row, not against the element that was drawing it.
Row height and the window
The same ten thousand rows at two row heights. The shorter rows fit more of the viewport, so more of them are rendered — and both counts stay bounded.
rowHeight: 28
0 rows in the DOM
rowHeight: 64
0 rows in the DOM
What stays true while you scroll
Two properties hold at any dataset size and on any machine, which is why they are the ones worth stating: the number of rendered rows stays bounded, and a scroll step costs the browser at most one layout. Neither is a timing figure — both are properties of the work being done rather than measurements of how long it took.
Turning features on does not change either of them. The first grid above is sorting, resizing, reordering and selecting while it scrolls, and its counter behaves exactly like one on a bare grid.
Scrolling with the keyboard
Page Down and Page Up move a viewport at a time, Ctrl+End jumps to the last row and Ctrl+Home back to the first. They travel the same window the mouse does — a keyboard jump to the end of ten thousand rows renders the same handful of rows a drag to the end would.
This is on by default. Set keyboardNav: false only if your application wants
those keys for itself.
Rows come back the way you left them
A row that scrolls out of view is removed from the page, and its element is reused for a row further down. That is what keeps the DOM small — and it is the thing readers are right to be nervous about, because a grid that recycles carelessly loses whatever you had done to the row.
It does not. Select a few rows in the second grid above, scroll far enough that they have left the page entirely, then scroll back: they are still selected. Everything the grid holds per row — selection, expansion, focus — is keyed to the row, never to the element that happened to be drawing it, so recycling an element cannot take any of it with it.
trackBy is what keeps that answer stable when your data changes rather
than your scroll position. Without it a row is identified by the object itself, which holds up
for as long as the objects do — and stops the moment a reload hands the grid a fresh array of
equal-but-different rows. Set it, and a row stays the same row across a refetch too.
Taller rows, smaller window
The window is derived from the viewport, so row height decides how many rows fit inside it.
Doubling rowHeight roughly halves the rendered count; it does not change the
fact that the count is bounded. The third example is the same ten thousand rows at two
heights, each with its own counter.
Nothing is left behind
Grids are mounted and destroyed constantly in a real application — a route change, a tab, a dialog. Seven interaction scenarios are each put through seven mount, use and destroy cycles on every build, and the result is zero grid instances still alive afterwards, with no listeners and no detached nodes left over.
Those are zeroes rather than sizes on purpose: a zero means the same thing on every machine, which is not true of a memory figure.
What decides the window
| Property | Type | Default | Description |
|---|---|---|---|
| config.height | number | string | — | The grid needs a height before it has a viewport to window — see Sizing & Height. |
| config.rowHeight | number | — | Decides how many rows fit the viewport, and therefore how large the rendered window is. |
| config.trackBy | string | ((row: T) => unknown) | — | Row identity. Recycling survives without it — a row is then identified by the object itself. Set it so identity also survives your data being replaced — see Row Identity. |
| config.keyboardNav | boolean | true | Arrow keys, Page Up / Page Down and Ctrl+Home / End move through the same window. Set false to hand those keys back to your application. |
| config.columnReorder | boolean | false | Grid-level switch for drag-to-reorder. A column's own draggable does nothing without it. |
| config.columnResize | boolean | false | Grid-level switch for drag-to-resize. A column's own resizable does nothing without it. |
| config.rowSelection | { mode: 'single' | 'multi' } | — | Selection is held per row, not per rendered element, so it survives the row leaving and re-entering the viewport. |