A virtual scroll React component for efficiently rendering large scrollable lists, grids, tables, and feeds
—
Responsive grid virtualization component that automatically adapts to container width and efficiently renders large datasets in a multi-column layout. Ideal for image galleries, card layouts, product catalogs, and any scenario requiring responsive grid display.
Grid virtualization component that automatically calculates column count based on container width and item dimensions, providing smooth scrolling performance for large datasets.
/**
* Grid virtualization component for responsive multi-column layouts
* @param props - Configuration options for the virtualized grid
* @returns JSX.Element representing the virtualized grid
*/
function VirtuosoGrid<D = any, C = any>(props: VirtuosoGridProps<D, C>): JSX.Element;
interface VirtuosoGridProps<D, C> extends GridRootProps {
/** The data items to be rendered. If data is set, totalCount will be inferred from the length */
data?: readonly D[];
/** The total amount of items to be rendered */
totalCount?: number;
/** Set the callback to specify the contents of each item */
itemContent?: GridItemContent<D, C>;
/** Use the components property for advanced customization of rendered elements */
components?: GridComponents<C>;
/** Additional context available in custom components and content callbacks */
context?: C;
/** If specified, the component will use the function to generate the key property for each list item */
computeItemKey?: GridComputeItemKey<D, C>;
/** Sets the grid items' className */
itemClassName?: string;
/** Sets the className for the list DOM element */
listClassName?: string;
/** Called with true / false when the list has reached the bottom / gets scrolled up */
atBottomStateChange?: (atBottom: boolean) => void;
/** Called with true / false when the list has reached the top / gets scrolled down */
atTopStateChange?: (atTop: boolean) => void;
/** Gets called when the user scrolls to the end of the list */
endReached?: (index: number) => void;
/** Called when the user scrolls to the start of the list */
startReached?: (index: number) => void;
/** Called with the new set of items each time the list items are rendered due to scrolling */
rangeChanged?: (range: ListRange) => void;
/** Called when the list starts/stops scrolling */
isScrolling?: (isScrolling: boolean) => void;
/** Pass a reference to a scrollable parent element */
customScrollParent?: HTMLElement;
/** Uses the document scroller rather than wrapping the grid in its own */
useWindowScroll?: boolean;
/** Use to display placeholders if the user scrolls fast through the list */
scrollSeekConfiguration?: false | ScrollSeekConfiguration;
/** Set the overscan property to make the component chunk the rendering of new items on scroll */
overscan?: number | { main: number; reverse: number };
/** Set the increaseViewportBy property to artificially increase the viewport size */
increaseViewportBy?: number | { top: number; bottom: number };
/** Use for server-side rendering - if set, the list will render the specified amount of items */
initialItemCount?: number;
/** Set to a value between 0 and totalCount - 1 to make the grid start scrolled to that item */
initialTopMostItemIndex?: GridIndexLocation;
/** Provides access to the root DOM element */
scrollerRef?: (ref: HTMLElement | null) => any;
/** Reports when the grid state changes - can be stored and passed back to restoreStateFrom */
stateChanged?: (state: GridStateSnapshot) => void;
/** Pass a state to restore the grid to the same state */
restoreStateFrom?: GridStateSnapshot | null | undefined;
/** Invoked with true after the grid has done the initial render and items have been measured */
readyStateChanged?: (ready: boolean) => void;
/** Set to LogLevel.DEBUG to enable various diagnostics in the console */
logLevel?: LogLevel;
}
type GridItemContent<D, C> = (index: number, data: D, context: C) => React.ReactNode;
type GridComputeItemKey<D, C> = (index: number, item: D, context: C) => React.Key;
type GridRootProps = Omit<React.HTMLProps<HTMLDivElement>, 'data' | 'ref'>;
type GridIndexLocation = FlatIndexLocationWithAlign | number;Usage Examples:
import React from 'react';
import { VirtuosoGrid } from 'react-virtuoso';
// Basic image gallery
function ImageGallery() {
const images = Array.from({ length: 1000 }, (_, i) => ({
id: i,
url: `https://picsum.photos/200/200?random=${i}`,
title: `Image ${i + 1}`
}));
return (
<VirtuosoGrid
style={{ height: '600px' }}
data={images}
itemContent={(index, image) => (
<div style={{ padding: '8px' }}>
<img
src={image.url}
alt={image.title}
style={{
width: '100%',
height: '200px',
objectFit: 'cover',
borderRadius: '8px'
}}
/>
<h4 style={{ margin: '8px 0', textAlign: 'center' }}>
{image.title}
</h4>
</div>
)}
/>
);
}
// Product card grid
function ProductGrid() {
const products = Array.from({ length: 500 }, (_, i) => ({
id: i,
name: `Product ${i + 1}`,
price: Math.floor(Math.random() * 1000) + 10,
rating: Math.floor(Math.random() * 5) + 1,
image: `https://picsum.photos/250/300?random=${i}`
}));
return (
<VirtuosoGrid
style={{ height: '80vh' }}
data={products}
listClassName="product-grid"
itemClassName="product-card"
itemContent={(index, product) => (
<div style={{
padding: '12px',
border: '1px solid #e0e0e0',
borderRadius: '8px',
backgroundColor: '#fff',
display: 'flex',
flexDirection: 'column',
height: '100%'
}}>
<img
src={product.image}
alt={product.name}
style={{
width: '100%',
height: '200px',
objectFit: 'cover',
borderRadius: '4px'
}}
/>
<div style={{ padding: '8px 0', flexGrow: 1 }}>
<h3 style={{ margin: '0 0 8px 0', fontSize: '16px' }}>
{product.name}
</h3>
<div style={{ marginBottom: '8px' }}>
{'★'.repeat(product.rating)}{'☆'.repeat(5 - product.rating)}
</div>
<div style={{ fontWeight: 'bold', color: '#2196f3' }}>
${product.price}
</div>
</div>
</div>
)}
/>
);
}
// Grid with infinite scrolling
function InfiniteGrid() {
const [items, setItems] = React.useState(
Array.from({ length: 100 }, (_, i) => ({
id: i,
content: `Card ${i + 1}`,
color: `hsl(${i * 30 % 360}, 70%, 80%)`
}))
);
const loadMore = () => {
setTimeout(() => {
setItems(prev => [
...prev,
...Array.from({ length: 50 }, (_, i) => ({
id: prev.length + i,
content: `Card ${prev.length + i + 1}`,
color: `hsl(${(prev.length + i) * 30 % 360}, 70%, 80%)`
}))
]);
}, 500);
};
return (
<VirtuosoGrid
style={{ height: '500px' }}
data={items}
endReached={loadMore}
itemContent={(index, item) => (
<div style={{
padding: '16px',
margin: '8px',
backgroundColor: item.color,
borderRadius: '8px',
textAlign: 'center',
minHeight: '120px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 'bold'
}}>
{item.content}
</div>
)}
/>
);
}
// Custom components grid
function CustomGrid() {
const items = Array.from({ length: 200 }, (_, i) => `Item ${i + 1}`);
return (
<VirtuosoGrid
style={{ height: '600px' }}
data={items}
components={{
Header: () => (
<div style={{
padding: '20px',
textAlign: 'center',
backgroundColor: '#f5f5f5',
fontSize: '24px',
fontWeight: 'bold'
}}>
Grid Header
</div>
),
Footer: () => (
<div style={{
padding: '20px',
textAlign: 'center',
backgroundColor: '#f5f5f5',
color: '#666'
}}>
End of grid
</div>
),
Item: ({ children, ...props }) => (
<div
{...props}
style={{
...props.style,
border: '2px solid #e0e0e0',
borderRadius: '12px',
overflow: 'hidden',
transition: 'transform 0.2s',
cursor: 'pointer'
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'scale(1.05)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'scale(1)';
}}
>
{children}
</div>
)
}}
itemContent={(index, item) => (
<div style={{ padding: '20px', textAlign: 'center' }}>
{item}
</div>
)}
/>
);
}Customize grid rendering through the components interface for headers, footers, items, and scroll behavior.
interface GridComponents<Context = any> {
/** Set to render a component at the bottom of the grid */
Footer?: React.ComponentType<ContextProp<Context>>;
/** Set to render a component at the top of the grid */
Header?: React.ComponentType<ContextProp<Context>>;
/** Set to customize the item wrapping element */
Item?: React.ComponentType<GridItemProps & ContextProp<Context>>;
/** Set to customize the items wrapper */
List?: React.ComponentType<GridListProps & ContextProp<Context>>;
/** Set to customize the outermost scrollable element */
Scroller?: React.ComponentType<ScrollerProps & ContextProp<Context>>;
/** Set to render an item placeholder when the user scrolls fast */
ScrollSeekPlaceholder?: React.ComponentType<GridScrollSeekPlaceholderProps & ContextProp<Context>>;
}
type GridItemProps = Pick<React.ComponentProps<'div'>, 'children' | 'className' | 'style'> &
React.RefAttributes<HTMLDivElement> & {
'data-index': number;
};
type GridListProps = Pick<React.ComponentProps<'div'>, 'children' | 'className' | 'style'> &
React.RefAttributes<HTMLDivElement> & {
'data-testid': string;
};
interface GridScrollSeekPlaceholderProps {
height: number;
index: number;
width: number;
}Track and restore grid state including viewport dimensions, scroll position, and item measurements.
interface GridStateSnapshot {
gap: Gap;
item: ElementDimensions;
scrollTop: number;
viewport: ElementDimensions;
}
interface Gap {
column: number;
row: number;
}
interface ElementDimensions {
height: number;
width: number;
}
interface GridItem<D> {
data?: D;
index: number;
}Usage Example:
function StatefulGrid() {
const [gridState, setGridState] = React.useState<GridStateSnapshot | null>(null);
const [isReady, setIsReady] = React.useState(false);
return (
<VirtuosoGrid
data={items}
restoreStateFrom={gridState}
stateChanged={setGridState}
readyStateChanged={setIsReady}
itemContent={(index, item) => (
<div style={{ padding: '16px', backgroundColor: isReady ? '#fff' : '#f5f5f5' }}>
{item}
</div>
)}
/>
);
}Performance optimization for fast scrolling with placeholder rendering.
interface ScrollSeekConfiguration {
enter: ScrollSeekToggle;
exit: ScrollSeekToggle;
change?: (velocity: number, range: ListRange) => void;
}
type ScrollSeekToggle = (velocity: number, range: ListRange) => boolean;Usage Example:
<VirtuosoGrid
scrollSeekConfiguration={{
enter: (velocity) => Math.abs(velocity) > 300,
exit: (velocity) => Math.abs(velocity) < 50,
}}
components={{
ScrollSeekPlaceholder: ({ height, width, index }) => (
<div
style={{
height,
width,
backgroundColor: '#f0f0f0',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '4px'
}}
>
{index}
</div>
)
}}
data={items}
itemContent={(index, item) => <div>{item}</div>}
/>Use the browser's main scrollbar instead of a contained scrollable area.
interface VirtuosoGridProps<D, C> {
/** Uses the document scroller rather than wrapping the grid in its own */
useWindowScroll?: boolean;
/** Pass a reference to a scrollable parent element */
customScrollParent?: HTMLElement;
}Usage Example:
// Full-page grid using window scroll
function FullPageGrid() {
return (
<VirtuosoGrid
useWindowScroll
data={items}
itemContent={(index, item) => (
<div style={{ padding: '20px', minHeight: '200px' }}>
{item}
</div>
)}
/>
);
}interface ListRange {
startIndex: number;
endIndex: number;
}
interface ContextProp<C> {
context: C;
}
interface ScrollerProps {
children: React.ReactNode;
style?: React.CSSProperties;
tabIndex?: number;
'data-testid'?: string;
'data-virtuoso-scroller'?: boolean;
}
enum LogLevel {
DEBUG,
INFO,
WARN,
ERROR
}Install with Tessl CLI
npx tessl i tessl/npm-react-virtuoso