VirtualScroller
Ten thousand rows, no sweat.
About
Windowed list for very large datasets: it mounts only the rows in the viewport, plus overscan above and below, inside a spacer the size of the whole list. Registers and full ledgers stay smooth at ten thousand lines and more. Scrolls down, across, or both. Cells have one fixed size.
Usage
Basic
renderItem is a property called once per row index while that row is in the window. Only the rows in the viewport plus the overscan are mounted; the spacer holds the scrollbar. show-index adds the bar that counts them.
Horizontal
orientation="horizontal" scrolls sideways instead: itemWidth is the cell width and width is the viewport, so the list is one row tall and no measurement is needed. renderItem still takes the index.
Grid
orientation="grid" windows both axes: itemCount is the row count, columns is the column count, and renderItem is called with the row and the column. Two hundred periods across two thousand accounts is four hundred thousand cells, of which a couple of hundred are in the page.
Paged from a server
suiRange reports the mounted window, and refresh() redraws it once the rows arrive. Keep renderItem stable: reassigning it also drops every row already drawn.
Jump to a row
scrollToIndex puts a row at the top of the viewport, which is how a search result is revealed.
API
<sui-virtual-scroller>
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
columns | columns | number | 0 | Column count for orientation="grid". A grid with no columns draws nothing. |
height | height | number | 280 | Viewport height in px. Ignored by the horizontal orientation, which is one row tall. |
itemCount | item-count | number | 0 | Total number of rows. In a grid this is the row count, not the cell count. |
itemHeight | item-height | number | 36 | Fixed row height in px; registers use 34 to 36. |
itemWidth | item-width | number | 160 | Fixed column width in px. Used by the horizontal and grid orientations, ignored by vertical. |
orientation | orientation | SuiVirtualScrollerOrientation"grid" | "horizontal" | "vertical" | 'vertical' | Scroll axis: down the rows, across the columns, or both. |
overscan | overscan | number | 4 | Extra rows rendered above and below the viewport. A grid overscans both axes. |
renderItem | property only | ((index: number, column?: number | undefined) => SuiRenderResult) | undefined | Row renderer (index, column) => string | HTMLElement. Strings render as text. Defaults to "Row n". A grid calls it once per cell, with the row index and the column index; every other orientation calls it with the index alone and leaves column undefined. Called once per cell while that cell stays in the window, so a renderer that builds a fresh element is not rebuilt on every scroll frame. Pass a stable function (a method, not a template arrow): the cached cells are dropped whenever this property changes identity, so a fresh arrow on every parent render redraws the whole window every time. When the rows suiRange asked for arrive, call refresh() instead. Property, not attribute. | |
showIndex | show-index | boolean | false | Shows a bar above the list that counts the rows and the ones rendered in the window, such as "12,480 rows ยท rendering 16 in the window", for a demo or a performance check. Off by default. |
width | width | number | 640 | Viewport width in px. Used by the horizontal and grid orientations; a vertical list takes the width of its container. |
Events
| Event | Detail | React | Vue | Description |
|---|---|---|---|---|
suiRange | SuiVirtualScrollerRangeDetail | onSuiRange | @sui-range | Fires when the mounted window changes, and once on the first render; detail is { start, end } with end exclusive, plus colStart and colEnd in a grid. Fetch start..end here to page a list from a server, then call refresh() when the rows arrive: props in, events out, no lazy flag. Does not bubble. |
Methods
| Method | Signature | Description |
|---|---|---|
refresh() | refresh(index?: number, column?: number) => Promise<void> | Re-runs renderItem for the cells in the window, or for one index when given. Call it when the data behind a suiRange request arrives, so the "pending" rows are redrawn without churning renderItem's identity (which would also throw away every cached cell). In a grid, an index with no column drops that whole row. |
scrollToIndex() | scrollToIndex(index: number, column?: number) => Promise<void> | Scrolls so that row index sits at the top of the viewport, and column column at its inline-start edge (the left, or the right under dir="rtl"). A horizontal list reads index as the column, since it has one row. |
setFocus() | setFocus() => Promise<void> | Focuses the scrolling viewport, the list's one tab stop, where the arrow keys and Page Up and Page Down scroll it. el.focus() on the host does the same. |
Types
SuiRenderResult type | |||
|---|---|---|---|
| No members. |
SuiVirtualScrollerOrientation typeWhich way the list scrolls. grid scrolls both ways over columns columns of rows. | |||
|---|---|---|---|
| No members. |
SuiVirtualScrollerRangeDetail interfaceThe mounted window. start is the first row index in the DOM and end is one past the last, so rows.slice(start, end) is exactly what is on screen. Includes overscan on both sides. colStart and colEnd are the same window over the columns, and only a grid reports them. | |||
|---|---|---|---|
start | number | ||
end | number | ||
colStart? | number | ||
colEnd? | number | ||
Theming
Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.
Color7
Typography1
Radius1
Elevation1
Every name above is listed with its value and its role in Foundations.