A Virtualizer renders a scrollable collection of data using customizable layouts. It supports very large collections by only rendering visible items to the DOM, reusing them as the user scrolls.
Layouts
Virtualizer uses Layout objects to determine the position and size of each item, and provide the list of currently visible items. When using a Virtualizer, all items are positioned by the Layout, and CSS layout properties such as flexbox and grid do not apply.
List
ListLayout places items along its orientation. Rows can be fixed or variable in size. When using a variable size, set the estimatedRowSize to a reasonable guess for how tall or wide the rows will be on average. This allows the size of the scrollbar to be calculated.
Use the orientation option to arrange items horizontally or vertically. Provide the same orientation on the collection component so keyboard navigation matches the layout.
Grid
GridLayout supports layout of items in an equal size grid. The items are sized between a minimum and maximum size depending on the width of the container. Make sure to set layout="grid" on the ListBox or GridList component as well so that keyboard navigation behavior is correct.
Waterfall
WaterfallLayout arranges variable height items in a column layout. The columns are sized between a minimum and maximum size depending on the width of the container.
Table
TableLayout provides layout of items in rows and columns, supporting virtualization of both horizontal and vertical scrolling. It should be used with the Table component. Rows can be fixed or variable height. When using variable heights, set the estimatedRowHeight to a reasonable guess for how tall the rows will be on average. This allows the size of the scrollbar to be calculated.
Dynamic item sizes
Use the shouldObserveItemSize prop to automatically re-layout when items change size.
Note that this uses a ResizeObserver internally and may have performance overhead.
import {Virtualizer, ListLayout} from 'react-aria-components/Virtualizer';
import {GridList, GridListItem, Text} from './GridList';
import {Button} from './Button';
import {useState} from 'react';
import {ChevronRight, ChevronDown} from 'lucide-react';
let items: {
id: string;
name: string;
}[] = [];
for (let i = 0; i < 10; i++) {
items.push({id: `item_${i}`, name: `Item ${i}`});
}
function ExpandableItem({item}) {
let [expanded, setExpanded] = useState(false);
return (
<div className="expandable-item">
<Text>{item.name}</Text>
<Button
variant="quiet"
onPress={() => setExpanded(!expanded)}
aria-label={expanded ? 'Collapse' : 'Expand'}>
{expanded ? <ChevronDown size={18} /> : <ChevronRight size={18} />}
</Button>
<Text slot="description" style={{display: expanded ? 'block' : 'none'}}>This is an expanded item.</Text>
</div>
);
}
<Virtualizer
layout={ListLayout}
layoutOptions={{
estimatedRowHeight: 25,
gap: 4,
}}
shouldObserveItemSize>
<GridList
style={{display: 'block', padding: 4, height: 400, width: '100%'}}
aria-label="virtualized with expandable rows"
items={items}>
{item =>
<GridListItem textValue={item.name} style={{padding: 0}}><ExpandableItem item={item} /></GridListItem>}
</GridList>
</Virtualizer>
Examples
API
Virtualizer
| Name | Type | |
|---|---|---|
children | ReactNode | |
The child collection to virtualize (e.g. ListBox, GridList, or Table). | ||
layout | LayoutClass | |
The layout object that determines the position and size of the visible elements. | ||
layoutOptions | O | |
Options for the layout. | ||
shouldObserveItemSize | boolean | |
Whether to observe each item's size with a ResizeObserver and re-measure when it changes. | ||
ListLayout
| Name | Type | Default |
|---|---|---|
orientation | Orientation | Default: 'vertical'
|
The primary orientation of the items. Usually this is the direction that the collection scrolls. | ||
rowSize | number | Default: 48
|
The fixed size of a row in px with respect to the applied orientation. | ||
estimatedRowSize | number | Default: — |
The estimated size of a row in px with respect to the applied orientation, when row sizes are variable. | ||
headingSize | number | Default: 48
|
The fixed size of a section header in px with respect to the applied orientation. | ||
estimatedHeadingSize | number | Default: — |
The estimated size of a section header in px with respect to the applied orientation, when heading sizes are variable. | ||
loaderSize | number | Default: 48
|
The fixed size of a loader element in px with respect to the applied orientation. This loader is specifically for "load more" elements rendered when loading more rows at the root level or inside nested row/sections. | ||
dropIndicatorThickness | number | Default: 2
|
The thickness of the drop indicator. | ||
gap | number | Default: 0
|
The gap between items. | ||
padding | number | Default: 0
|
The padding around the list. | ||
GridLayout
| Name | Type | Default |
|---|---|---|
minItemSize | Size | Default: 200 x 200
|
The minimum item size. | ||
maxItemSize | Size | Default: Infinity
|
The maximum item size. | ||
preserveAspectRatio | boolean | Default: false
|
Whether to preserve the aspect ratio of the | ||
minSpace | Size | Default: 18 x 18
|
The minimum space required between items. | ||
maxHorizontalSpace | number | Default: Infinity
|
The maximum allowed horizontal space between items. | ||
maxColumns | number | Default: Infinity
|
The maximum number of columns. | ||
dropIndicatorThickness | number | Default: 2
|
The thickness of the drop indicator. | ||
loaderHeight | number | Default: 48
|
The fixed height of a loader element in px. This loader is specifically for "load more" elements rendered when loading more rows at the root level or inside nested row/sections. | ||
WaterfallLayout
| Name | Type | Default |
|---|---|---|
minItemSize | Size | Default: 200 x 200
|
The minimum item size. | ||
maxItemSize | Size | Default: Infinity
|
The maximum item size. | ||
minSpace | Size | Default: 18 x 18
|
The minimum space required between items. | ||
maxHorizontalSpace | number | Default: Infinity
|
The maximum allowed horizontal space between items. | ||
maxColumns | number | Default: Infinity
|
The maximum number of columns. | ||
dropIndicatorThickness | number | Default: 2
|
The thickness of the drop indicator. | ||
loaderHeight | number | Default: 48
|
The fixed height of a loader element in px. This loader is specifically for "load more" elements rendered when loading more rows at the root level or inside nested row/sections. | ||
TableLayout
| Name | Type | Default |
|---|---|---|
rowHeight | number | Default: 48
|
The fixed height of a row in px. | ||
estimatedRowHeight | number | Default: — |
The estimated height of a row, when row heights are variable. | ||
headingHeight | number | Default: 48
|
The fixed height of a section header in px. | ||
estimatedHeadingHeight | number | Default: — |
The estimated height of a section header, when the height is variable. | ||
loaderHeight | number | Default: 48
|
The fixed height of a loader element in px. This loader is specifically for "load more" elements rendered when loading more rows at the root level or inside nested row/sections. | ||
columnWidths | Map | Default: — |
dropIndicatorThickness | number | Default: 2
|
The thickness of the drop indicator. | ||
gap | number | Default: 0
|
The gap between items. | ||
padding | number | Default: 0
|
The padding around the list. | ||