packages/react-aria-components/docs/Virtualizer.mdx
{/* Copyright 2020 Adobe. All rights reserved. This file is licensed to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */}
import {Layout} from '@react-spectrum/docs'; export default Layout;
import docs from 'docs:react-aria-components'; import sharedDocs from 'docs:@react-types/shared'; import {PropTable, HeaderInfo, TypeLink, PageDescription, StateTable, ContextTable, ClassAPI, VersionBadge, InterfaceType, TypeContext} from '@react-spectrum/docs'; import styles from '@react-spectrum/docs/src/docs.css'; import packageData from 'react-aria-components/package.json'; import ChevronRight from '@spectrum-icons/workflow/ChevronRight'; import {Divider} from '@react-spectrum/divider'; import {ExampleCard} from '@react-spectrum/docs/src/ExampleCard'; import {ExampleList} from '@react-spectrum/docs/src/ExampleList'; import {Keyboard} from '@react-spectrum/text'; import Collections from '@react-spectrum/docs/pages/assets/component-illustrations/Collections.svg'; import {StarterKits} from '@react-spectrum/docs/src/StarterKits'; import listboxUtils from 'docs:@react-aria/test-utils'; import {InlineAlert, Content, Heading} from '@adobe/react-spectrum';
<PageDescription>{docs.exports.Virtualizer.description}</PageDescription>
<HeaderInfo packageData={packageData} componentNames={['Virtualizer']} />
@import "@react-aria/example-theme";
@import './Checkbox.mdx' layer(checkbox);
@import './ListBox.mdx' layer(listbox);
@import './GridList.mdx' layer(gridlist);
@import './Table.mdx' layer(table);
.react-aria-ListBox,
.react-aria-GridList {
display: block;
padding: 0;
height: 300px;
width: 250px;
}
.react-aria-Table {
padding: 0;
}
import {Virtualizer, ListLayout, ListBox, ListBoxItem} from 'react-aria-components';
let items = [];
for (let i = 0; i < 5000; i++) {
items.push({id: i, name: `Item ${i}`});
}
function Example() {
return (
<Virtualizer
layout={ListLayout}
layoutOptions={{
rowHeight: 32,
padding: 4,
gap: 4
}}>
<ListBox aria-label="Virtualized ListBox" selectionMode="multiple" items={items}>
{item => <ListBoxItem>{item.name}</ListBoxItem>}
</ListBox>
</Virtualizer>
);
}
Virtualized scrolling is a performance optimization for large lists. Instead of rendering all items to the DOM at once, it only renders the visible items, reusing them as the user scrolls. This results in a small number of DOM elements being rendered, reducing memory usage and improving browser layout and rendering performance.
aria-rowindex to give screen reader users context.Collection components such as ListBox, GridList, Tree, Menu, and Table can be virtualized by wrapping them in a <<TypeLink links={docs.links} type={docs.exports.Virtualizer} />>, and providing a <TypeLink links={docs.links} type={docs.exports.Layout} /> object such as <TypeLink links={docs.links} type={docs.exports.ListLayout} /> or <TypeLink links={docs.links} type={docs.exports.GridLayout} />. See below for examples of each layout.
import {Virtualizer, ListLayout} from 'react-aria-components';
<Virtualizer layout={ListLayout}>
<ListBox>
</ListBox>
</Virtualizer>
Virtualizer uses <TypeLink links={docs.links} type={docs.exports.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.
ListLayout supports layout of items in a vertical stack. 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. ListLayout supports the following options:
<TypeContext.Provider value={docs.links}> <InterfaceType {...docs.exports.ListLayoutOptions} /> </TypeContext.Provider>
This example shows a GridList with variable height rows due to text wrapping.
let lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin sit amet tristique risus. In sit amet suscipit lorem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In condimentum imperdiet metus non condimentum. Duis eu velit et quam accumsan tempus at id velit. Duis elementum elementum purus, id tempus mauris posuere a. Nunc vestibulum sapien pellentesque lectus commodo ornare.'.split(' ');
let items = [];
for (let i = 0; i < 5000; i++) {
let words = Math.max(2, Math.floor(Math.random() * 25));
let name = lorem.slice(0, words).join(' ');
items.push({id: i, name});
}
import {Virtualizer, ListLayout} from 'react-aria-components';
import {MyGridList, MyItem} from './GridList';
function Example() {
return (
<Virtualizer
/*- begin highlight -*/
layout={ListLayout}
layoutOptions={{
estimatedRowHeight: 75,
gap: 4,
padding: 4
}}
/*- end highlight -*/
>
<MyGridList aria-label="Virtualized GridList" selectionMode="multiple" items={items}>
{item => <MyItem>{item.name}</MyItem>}
</MyGridList>
</Virtualizer>
);
}
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. It supports the following options:
<TypeContext.Provider value={docs.links}> <InterfaceType {...docs.exports.GridLayoutOptions} /> </TypeContext.Provider>
Make sure to set layout="grid" on the ListBox or GridList component as well so that keyboard navigation behavior is correct.
let albumOptions = [
{
image: 'https://images.unsplash.com/photo-1593958812614-2db6a598c71c?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Nnx8ZGlzY298ZW58MHx8MHx8fDA%3D&auto=format&fit=crop&w=900&q=60',
title: 'Euphoric Echoes',
artist: 'Luna Solstice'
},
{
image: 'https://images.unsplash.com/photo-1601042879364-f3947d3f9c16?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8M3x8bmVvbnxlbnwwfHwwfHx8MA%3D%3D&auto=format&fit=crop&w=900&q=60',
title: 'Neon Dreamscape',
artist: 'Electra Skyline'
},
{
image: 'https://images.unsplash.com/photo-1528722828814-77b9b83aafb2?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTF8fHNwYWNlfGVufDB8fDB8fHww&auto=format&fit=crop&w=900&q=60',
title: 'Cosmic Serenade',
artist: 'Orion\'s Symphony'
},
{
image: 'https://images.unsplash.com/photo-1511379938547-c1f69419868d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8M3x8bXVzaWN8ZW58MHx8MHx8fDA%3D&auto=format&fit=crop&w=900&q=60',
title: 'Melancholy Melodies',
artist: 'Violet Mistral'
},
{
image: 'https://images.unsplash.com/photo-1608433319511-dfe8ea4cbd3c?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTF8fGJlYXR8ZW58MHx8MHx8fDA%3D&auto=format&fit=crop&w=900&q=60',
title: 'Rhythmic Illusions',
artist: 'Mirage Beats'
}
];
let albums = [];
for (let i = 0; i < 1000; i++) {
albums.push({
id: i,
...albumOptions[i % albumOptions.length]
})
}
import {GridLayout, Size, Text} from 'react-aria-components';
function Example() {
return (
<div className="resizable">
<Virtualizer
/*- begin highlight -*/
layout={GridLayout}
layoutOptions={{
minItemSize: new Size(100, 140),
minSpace: new Size(8, 8)
}}
/*- end highlight -*/
>
<ListBox
/*- begin highlight -*/
layout="grid"
/*- end highlight -*/
aria-label="Virtualized grid layout"
selectionMode="multiple"
items={albums}>
{item => (
<ListBoxItem textValue={item.title}>
<Text slot="label">{item.title}</Text>
<Text slot="description">{item.artist}</Text>
</ListBoxItem>
)}
</ListBox>
</Virtualizer>
</div>
);
}
.resizable {
resize: horizontal;
width: 400px;
min-width: 240px;
max-width: 100%;
overflow: hidden;
}
.react-aria-ListBox[data-layout=grid] {
max-width: none;
width: 100%;
[slot=label] {
font-size: 12px;
}
}
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. It supports the following options:
<TypeContext.Provider value={docs.links}> <InterfaceType {...docs.exports.WaterfallLayoutOptions} /> </TypeContext.Provider>
let images = [
{
"id": "8SXaMMWCTGc",
"title": "A Ficus Lyrata Leaf in the sunlight (2/2) (IG: @clay.banks)",
"user": "Clay Banks",
"image": "https://images.unsplash.com/photo-1580133318324-f2f76d987dd8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.6666"
},
{
"id": "pYjCqqDEOFo",
"title": "beach of Italy",
"user": "alan bajura",
"image": "https://images.unsplash.com/photo-1737100522891-e8946ac97fd1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.6666666666666666"
},
{
"id": "CF-2tl6MQj0",
"title": "A winding road in the middle of a forest",
"user": "Artem Stoliar",
"image": "https://images.unsplash.com/photo-1738249034651-1896f689be58?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "1.3333333333333333"
},
{
"id": "OW97sLU0cOw",
"title": "A green and purple aurora over a snow covered forest",
"user": "Janosch Diggelmann",
"image": "https://images.unsplash.com/photo-1738189669835-61808a9d5981?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.6669921875"
},
{
"id": "WfeLZ02IhkM",
"title": "A blue and white firework is seen from above",
"user": "Janosch Diggelmann",
"image": "https://images.unsplash.com/photo-1738168601630-1c1f3ef5a95a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "1.3353596757852078"
},
{
"id": "w1GpST72Bg8",
"title": "A snow covered mountain with a sky background",
"user": "Daniil Silantev",
"image": "https://images.unsplash.com/photo-1738165170747-ecc6e3a4d97c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "1.4978580171358629"
},
{
"id": "0iN0KIt6lYI",
"title": "\"Pastel Sunset\"",
"user": "Marek Piwnicki",
"image": "https://images.unsplash.com/photo-1737917818689-f3b3708de5d7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.6249763481551561"
},
{
"id": "-mFKPfXXUG0",
"title": "Leave the weight behind! You must make yourself light to strive upwards — to reach the light. (A serene winter landscape featuring a dense collection of bare, white trees.)",
"user": "Simon Berger",
"image": "https://images.unsplash.com/photo-1737972970322-cc2e255021bd?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "1"
},
{
"id": "MOk6URQ28R4",
"title": "A snow covered tree with a sky background",
"user": "Daniil Silantev",
"image": "https://images.unsplash.com/photo-1738081359113-a7a33c509cf9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.666598611678236"
},
{
"id": "y36Nj_edtRE",
"title": "A lake surrounded by trees covered in snow",
"user": "Daniel Seßler",
"image": "https://images.unsplash.com/photo-1736018545810-3de4c7ec25fa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.667"
},
{
"id": "NvBV-YwlgBw",
"title": "The night sky with stars above a rock formation",
"user": "Dennis Haug",
"image": "https://images.unsplash.com/photo-1735528655501-cf671a3323c3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "1"
},
{
"id": "UthQdrPFxt0",
"title": "A pine tree covered in snow in a forest",
"user": "Anita Austvika",
"image": "https://images.unsplash.com/photo-1737312905026-5dfdff1097bc?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.6666666666666666"
},
{
"id": "2k74xaf8dfc",
"title": "The sun shines through the trees in the forest",
"user": "Joyce G",
"image": "https://images.unsplash.com/photo-1736185597807-371cae1c7e4e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.6666666666666666"
},
{
"id": "Yje5kgfvCm0",
"title": "A blurry photo of a field of flowers",
"user": "Eugene Golovesov",
"image": "https://images.unsplash.com/photo-1736483065204-e55e62092780?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.6661569826707442"
},
{
"id": "G2bsj2LVttI",
"title": "A foggy road lined with trees and grass",
"user": "Ingmar H",
"image": "https://images.unsplash.com/photo-1737903071772-4d20348b4d81?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.7499509707785841"
},
{
"id": "ppyNBOkfiuY",
"title": "A close up of a green palm tree",
"user": "Junel Mujar",
"image": "https://images.unsplash.com/photo-1736849544918-6ddb5cfc2c42?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.7507507507507507"
},
{
"id": "UcWUMqIsld8",
"title": "A green leaf floating on top of a body of water",
"user": "Allec Gomes",
"image": "https://images.unsplash.com/photo-1737559217439-a5703e9b65cb?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.6666666666666666"
},
{
"id": "xHqOVq9w8OI",
"title": "green-leafed plant",
"user": "Joshua Michaels",
"image": "https://images.unsplash.com/photo-1563364664-399838d1394c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "1.504"
},
{
"id": "uWx3_XEc-Jw",
"title": "A view of a mountain covered in fog",
"user": "iuliu illes",
"image": "https://images.unsplash.com/photo-1737403428945-c584529b7b17?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "1.3430962343096233"
},
{
"id": "2_3lhGt8i-Y",
"title": "A field with tall grass and fog in the background",
"user": "Ingmar H",
"image": "https://images.unsplash.com/photo-1737439987404-a3ee9fb95351?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.6666666666666666"
},
{
"id": "FV-__IOxb08",
"title": "A close up of a wave on a sandy beach",
"user": "Jonathan Borba",
"image": "https://images.unsplash.com/photo-1726502102472-2108ef2a5cae?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.6666666666666666"
},
{
"id": "_BS-vK3boOU",
"title": "Desert textures",
"user": "Braden Jarvis",
"image": "https://images.unsplash.com/photo-1722359546494-8e3a00f88e95?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.7135258358662614"
},
{
"id": "LjAcS9lJdBg",
"title": "Tew Falls, waterfall, in Hamilton, Canada.",
"user": "Andre Portolesi",
"image": "https://images.unsplash.com/photo-1705021246536-aecfad654893?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.8"
},
{
"id": "hlj6xJG30FE",
"title": "Find me on Instagram! @intricateexplorer",
"user": "Intricate Explorer",
"image": "https://images.unsplash.com/photo-1631641551473-fbe46919289d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "1.4992510164776376"
},
{
"id": "vMoZvKeZOhw",
"title": "Salt Marshes, Isle of Harris, Scotland by Nils Leonhardt. Visit my website: https://nilsleonhardt.com/storytelling-harris/ Instagram: @am.basteir",
"user": "Nils Leonhardt",
"image": "https://images.unsplash.com/photo-1585951301678-8fd6f3b32c7e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.6666666666666666"
},
{
"id": "wCLCK9LDDjI",
"title": "An aerial view of a snow covered forest",
"user": "Lukas Hädrich",
"image": "https://images.unsplash.com/photo-1737405555489-78b3755eaa81?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "1.5"
},
{
"id": "OdDx3_NB-Wk",
"title": "A close up of a tall grass with a sky in the background",
"user": "Ingmar H",
"image": "https://images.unsplash.com/photo-1737301519296-062cd324dbfa?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.6666666666666666"
},
{
"id": "Gn-FOw1geFc",
"title": "Larches on Maple Pass, Washington",
"user": "noelle",
"image": "https://images.unsplash.com/photo-1737496538329-a59d10148a08?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.6666666666666666"
},
{
"id": "VhKJHOz2tJ8",
"title": "IC 1805 La nébuleuse du coeur",
"user": "arnaud girault",
"image": "https://images.unsplash.com/photo-1737478598284-b9bc11cb1e9b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "1.504158004158004"
},
{
"id": "w5QmH_uqB0U",
"title": "A pile of shells sitting on top of a sandy beach",
"user": "Toa Heftiba",
"image": "https://images.unsplash.com/photo-1725366351350-a64a1be919ef?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNDA4NDh8MHwxfHRvcGljfHw2c01WalRMU2tlUXx8fHx8Mnx8MTczODM2NzE4M3w&ixlib=rb-4.0.3&q=80&w=400",
"aspectRatio": "0.6666666666666666"
}
];
for (let i = 0; images.length < 500; i++) {
images.push({...images[i % 30], id: String(i)});
}
import {WaterfallLayout, Size, Text} from 'react-aria-components';
function Example() {
return (
<Virtualizer
/*- begin highlight -*/
layout={WaterfallLayout}
layoutOptions={{
minItemSize: new Size(150, 150),
minSpace: new Size(8, 8)
}}
/*- end highlight -*/
>
<ListBox
layout="grid"
aria-label="Virtualized waterfall layout"
selectionMode="multiple"
items={images}>
{item => (
<ListBoxItem textValue={item.title}>
<Text slot="label">{item.title}</Text>
<Text slot="description">{item.user}</Text>
</ListBoxItem>
)}
</ListBox>
</Virtualizer>
);
}
.react-aria-ListBox[aria-label~=waterfall] {
height: 400px;
max-height: none;
.react-aria-ListBoxItem img {
max-width: none;
}
}
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. TableLayout supports the following options:
<TypeContext.Provider value={docs.links}> <InterfaceType {...docs.exports.ListLayoutOptions} /> </TypeContext.Provider>
import {TableLayout, Table, TableHeader, Column, TableBody, Row, Cell} from 'react-aria-components';
import {MyCheckbox} from './Checkbox';
let rows = [];
for (let i = 0; i < 1000; i++) {
rows.push({id: i, foo: `Foo ${i}`, bar: `Bar ${i}`, baz: `Baz ${i}`});
}
function Example() {
return (
<Virtualizer
/*- begin highlight -*/
layout={TableLayout}
layoutOptions={{
rowHeight: 32,
headingHeight: 32,
padding: 4,
gap: 4
}}
/*- end highlight -*/
>
<Table aria-label="Virtualized Table" selectionMode="multiple">
<TableHeader>
<Column width={40} minWidth={0}><MyCheckbox slot="selection" /></Column>
<Column isRowHeader>Foo</Column>
<Column>Bar</Column>
<Column>Baz</Column>
</TableHeader>
<TableBody items={rows}>
{item => (
<Row style={{width: 'inherit', height: 'inherit'}}>
<Cell><MyCheckbox slot="selection" /></Cell>
<Cell>{item.foo}</Cell>
<Cell>{item.bar}</Cell>
<Cell>{item.baz}</Cell>
</Row>
)}
</TableBody>
</Table>
</Virtualizer>
);
}
.react-aria-Table {
width: 400;
height: 300;
overflow: auto;
scroll-padding-top: 38px;
}
.react-aria-TableHeader {
background: var(--spectrum-gray-100);
width: 100%;
height: 100%;
border-radius: 8px;
font-weight: bold;
}
.react-aria-Cell,
.react-aria-Column {
display: flex;
align-items: center;
height: 100%;
box-sizing: border-box;
}
Custom Virtualizer layouts can be created by extending the Layout abstract base class. At a minimum, the getVisibleLayoutInfos, getLayoutInfo, and getContentSize methods must be implemented. You can override the other methods to customize their default behavior.
<TypeContext.Provider value={docs.links}> <InterfaceType {...docs.exports.Layout} hideProperties /> </TypeContext.Provider>
Layouts produce LayoutInfo objects describing the position, size, and other properties of each item in a collection. Virtualizer requests this information when needed, and uses it to create DOM nodes to display.
<TypeContext.Provider value={docs.links}> <InterfaceType {...docs.exports.LayoutInfo} hideMethods /> </TypeContext.Provider>
This example implements a horizontally scrolling layout with fixed size items.
import {Layout, LayoutInfo, Rect, Size, Key} from 'react-aria-components';
class HorizontalLayout extends Layout {
// Determine which items are visible within the given rectangle.
getVisibleLayoutInfos(rect: Rect): LayoutInfo[] {
let virtualizer = this.virtualizer!;
let keys = Array.from(virtualizer.collection.getKeys());
let startIndex = Math.max(0, Math.floor(rect.x / 100));
let endIndex = Math.min(keys.length - 1, Math.ceil(rect.maxX / 100));
let layoutInfos = [];
for (let i = startIndex; i <= endIndex; i++) {
layoutInfos.push(this.getLayoutInfo(keys[i]));
}
// Always add persisted keys (e.g. the focused item), even when out of view.
for (let key of virtualizer.persistedKeys) {
let item = virtualizer.collection.getItem(key);
if (item?.index < startIndex) {
layoutInfos.unshift(this.getLayoutInfo(key));
} else if (item?.index > endIndex) {
layoutInfos.push(this.getLayoutInfo(key));
}
}
return layoutInfos;
}
// Provide a LayoutInfo for a specific item.
getLayoutInfo(key: Key): LayoutInfo | null {
let node = this.virtualizer!.collection.getItem(key);
if (!node) {
return null;
}
let rect = new Rect(node.index * 100, 0, 100, 100);
return new LayoutInfo(node.type, node.key, rect);
}
// Provide the total size of all items.
getContentSize(): Size {
let numItems = this.virtualizer!.collection.size;
return new Size(numItems * 100, 100);
}
}
function Example() {
let items = [];
for (let i = 0; i < 200; i++) {
items.push({id: i, name: `Item ${i}`});
}
return (
<Virtualizer layout={HorizontalLayout}>
<ListBox aria-label="Favorite animal" items={items} orientation="horizontal" style={{height: 'fit-content'}}>
{item => <ListBoxItem className="item">{item.name}</ListBoxItem>}
</ListBox>
</Virtualizer>
);
}
.item {
background: gray;
padding: 4px;
background-clip: content-box;
height: 100%;
box-sizing: border-box;
}