A virtual scroll React component for efficiently rendering large scrollable lists, grids, tables, and feeds
—
Core list virtualization component that efficiently renders large datasets by only displaying visible items plus a configurable buffer. Automatically handles variable-sized items without manual measurements.
Main virtualization component for rendering lists with automatic size detection and scroll optimization.
/**
* Main virtualization component for rendering lists with automatic size detection
* @param props - Configuration options for the virtualized list
* @returns JSX.Element representing the virtualized list
*/
function Virtuoso<D = any, C = any>(props: VirtuosoProps<D, C>): JSX.Element;
interface VirtuosoProps<D, C> extends ListRootProps {
/** 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?: ItemContent<D, C>;
/** Use the components property for advanced customization of rendered elements */
components?: Components<D, 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?: ComputeItemKey<D, C>;
/** Can be used to improve performance if the rendered items are of known size */
fixedItemHeight?: number;
/** By default, the component assumes the default item height from the first rendered item */
defaultItemHeight?: number;
/** If set to true, the list automatically scrolls to bottom if the total count is changed */
followOutput?: FollowOutput;
/** 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 with the new set of items each time the list items are rendered due to scrolling */
itemsRendered?: (items: ListItem<D>[]) => void;
/** 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;
/** Use when implementing inverse infinite scrolling */
firstItemIndex?: number;
/** Set to a value between 0 and totalCount - 1 to make the list start scrolled to that item */
initialTopMostItemIndex?: IndexLocationWithAlign | number;
/** Set this value to offset the initial location of the list */
initialScrollTop?: number;
/** Setting alignToBottom to true aligns the items to the bottom of the list if shorter than viewport */
alignToBottom?: boolean;
/** Uses the document scroller rather than wrapping the list in its own */
useWindowScroll?: boolean;
/** Pass a reference to a scrollable parent element */
customScrollParent?: HTMLElement;
/** 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 };
/** Called when the list starts/stops scrolling */
isScrolling?: (isScrolling: boolean) => void;
/** Provides access to the root DOM element */
scrollerRef?: (ref: HTMLElement | null | Window) => any;
/** Pass a state obtained from getState() method to restore the list state */
restoreStateFrom?: StateSnapshot;
/** Set the amount of items to remain fixed at the top of the list */
topItemCount?: number;
/** Called when the total list height is changed due to new items or viewport resize */
totalListHeightChanged?: (height: number) => void;
/** Allows customizing the height/width calculation of Item elements */
itemSize?: SizeFunction;
/** Use for server-side rendering - if set, the list will render the specified amount of items */
initialItemCount?: number;
/** When set, turns the scroller into a horizontal list */
horizontalDirection?: boolean;
/** Implement this callback to adjust list position when total count changes */
scrollIntoViewOnChange?: (params: {
context: C;
totalCount: number;
scrollingInProgress: boolean;
}) => ScrollIntoViewLocation | null | undefined | false | void;
/** Set to customize the wrapper tag for header and footer components (default is 'div') */
headerFooterTag?: string;
/** Set to LogLevel.DEBUG to enable various diagnostics in the console */
logLevel?: LogLevel;
/** By default 4. Redefine to change how much away from the bottom the scroller can be */
atBottomThreshold?: number;
/** By default 0. Redefine to change how much away from the top the scroller can be */
atTopThreshold?: number;
/** When set, the resize observer will not use requestAnimationFrame to report size changes */
skipAnimationFrameInResizeObserver?: boolean;
}
type ItemContent<D, C> = (index: number, data: D, context: C) => React.ReactNode;
type ComputeItemKey<D, C> = (index: number, item: D, context: C) => React.Key;
type ListRootProps = Omit<React.HTMLProps<HTMLDivElement>, 'data' | 'ref'>;
type ScrollIntoViewLocation = FlatScrollIntoViewLocation | GroupedScrollIntoViewLocation;
interface FlatScrollIntoViewLocation extends ScrollIntoViewLocationOptions {
index: number;
}
interface GroupedScrollIntoViewLocation extends ScrollIntoViewLocationOptions {
groupIndex: number;
}
interface ScrollIntoViewLocationOptions {
align?: 'center' | 'end' | 'start';
behavior?: 'auto' | 'smooth';
calculateViewLocation?: CalculateViewLocation;
done?: () => void;
}Usage Examples:
import React from 'react';
import { Virtuoso } from 'react-virtuoso';
// Basic list with data array
function BasicList() {
const items = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i}`,
value: Math.random()
}));
return (
<Virtuoso
style={{ height: '400px' }}
data={items}
itemContent={(index, item) => (
<div style={{ padding: '12px', borderBottom: '1px solid #eee' }}>
<strong>{item.name}</strong>
<div>Value: {item.value.toFixed(3)}</div>
</div>
)}
/>
);
}
// List with totalCount (without data array)
function CountBasedList() {
return (
<Virtuoso
style={{ height: '400px' }}
totalCount={100000}
itemContent={(index) => (
<div style={{ padding: '12px' }}>
Item {index}
</div>
)}
/>
);
}
// List with infinite scrolling
function InfiniteList() {
const [items, setItems] = React.useState(
Array.from({ length: 100 }, (_, i) => `Item ${i}`)
);
const loadMore = () => {
setItems(prev => [
...prev,
...Array.from({ length: 50 }, (_, i) => `Item ${prev.length + i}`)
]);
};
return (
<Virtuoso
style={{ height: '400px' }}
data={items}
endReached={loadMore}
itemContent={(index, item) => (
<div style={{ padding: '12px' }}>
{item}
</div>
)}
/>
);
}
// List with custom components
function CustomComponentsList() {
const items = Array.from({ length: 1000 }, (_, i) => `Item ${i}`);
return (
<Virtuoso
style={{ height: '400px' }}
data={items}
components={{
Header: () => <div style={{ padding: '12px', fontWeight: 'bold' }}>Header</div>,
Footer: () => <div style={{ padding: '12px', fontWeight: 'bold' }}>Footer</div>,
EmptyPlaceholder: () => <div>No items to display</div>,
Item: ({ children, ...props }) => (
<div {...props} style={{ padding: '8px', margin: '4px', backgroundColor: '#f5f5f5' }}>
{children}
</div>
)
}}
itemContent={(index, item) => item}
/>
);
}Automatically scroll to bottom when new items are added, perfect for chat interfaces and live feeds.
/**
* Configure automatic scrolling behavior when new items are added
*/
type FollowOutput = FollowOutputCallback | FollowOutputScalarType;
type FollowOutputCallback = (isAtBottom: boolean) => FollowOutputScalarType;
type FollowOutputScalarType = 'auto' | 'smooth' | boolean;Usage Example:
// Always follow output with smooth scrolling
<Virtuoso
followOutput="smooth"
data={messages}
itemContent={(index, message) => <div>{message.text}</div>}
/>
// Conditional follow output
<Virtuoso
followOutput={(isAtBottom) => {
// Only auto-scroll if user is already at bottom
return isAtBottom ? 'smooth' : false;
}}
data={messages}
itemContent={(index, message) => <div>{message.text}</div>}
/>Display placeholders during fast scrolling to improve performance.
interface ScrollSeekConfiguration {
/** Callback to determine if the list should enter scroll seek mode */
enter: ScrollSeekToggle;
/** Callback to determine if the list should exit scroll seek mode */
exit: ScrollSeekToggle;
/** Called during scrolling in scroll seek mode - use to display a hint where the list is */
change?: (velocity: number, range: ListRange) => void;
}
type ScrollSeekToggle = (velocity: number, range: ListRange) => boolean;
interface ScrollSeekPlaceholderProps {
height: number;
index: number;
type: 'group' | 'item';
groupIndex?: number;
}Usage Example:
<Virtuoso
scrollSeekConfiguration={{
enter: (velocity) => Math.abs(velocity) > 200,
exit: (velocity) => Math.abs(velocity) < 30,
}}
components={{
ScrollSeekPlaceholder: ({ height, index }) => (
<div style={{ height, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
Loading item {index}...
</div>
)
}}
data={items}
itemContent={(index, item) => <div>{item}</div>}
/>/** Calculates the height of el, which will be the Item element in the DOM */
type SizeFunction = (el: HTMLElement, field: 'offsetHeight' | 'offsetWidth') => number;
interface StateSnapshot {
ranges: SizeRange[];
scrollTop: number;
}
interface SizeRange {
startIndex: number;
endIndex: number;
size: number;
}
type StateCallback = (state: StateSnapshot) => void;Usage Example:
function OptimizedList() {
const [state, setState] = React.useState<StateSnapshot | null>(null);
return (
<Virtuoso
// Fixed height for better performance
fixedItemHeight={50}
// Reduce overscan for memory efficiency
overscan={{ main: 5, reverse: 5 }}
// Restore previous state
restoreStateFrom={state}
// Custom size calculation
itemSize={(el) => el.getBoundingClientRect().height}
data={items}
itemContent={(index, item) => <div>{item}</div>}
/>
);
}interface ListItem<D> {
data?: D;
index: number;
offset: number;
size: number;
}
interface ListRange {
startIndex: number;
endIndex: number;
}
interface Item<D> {
data?: D;
index: number;
offset: number;
size: number;
}
enum LogLevel {
DEBUG,
INFO,
WARN,
ERROR
}
interface LocationOptions {
align?: 'center' | 'end' | 'start';
behavior?: 'auto' | 'smooth';
offset?: number;
}
interface FlatIndexLocationWithAlign extends LocationOptions {
index: 'LAST' | number;
}
type IndexLocationWithAlign = FlatIndexLocationWithAlign;Install with Tessl CLI
npx tessl i tessl/npm-react-virtuoso