Close
Angular React Web Components Blazor React
Open Source

Virtual Scroll Component

The Ignite UI for React Virtual Scroll is a component that renders large lists by keeping only the items in its viewport, plus a configurable buffer, in the DOM. The scrollbar still spans the whole collection, so a virtual list of a hundred thousand items scrolls like a regular list.

Live Demo

Anatomy

The React Virtual Scroll renders the visible items plus a configurable buffer, and its track preserves the scroll range of the whole collection.

React Virtual Scroll anatomy: host, track, content element, item wrappers, and over-scan buffers
1. Host: The scroll container. Its fixed height (width when horizontal) sets how many items are visible.
2. Track: A spacer sized to the estimated length of the whole collection, so the scrollbar spans every item.
3. Content element: Holds only the rendered items. It starts at the first rendered buffer item, above the viewport, and takes its size from the rendered items.
4. Item wrapper: One per rendered item. It hosts the item template and is the box that gets measured.
5. Over-scan buffer: The overScan items (2 by default) rendered past each edge of the viewport.
igc-virtual-scroll                            — scrollable viewport
└── [part="virtualization-track"]             — provides the collection's scroll range
    └── [part="virtualization-content"]       — positions the rendered window
        └── div[data-vs-index]                — one wrapper per rendered item; hosts the item template

Getting Started

Set up Ignite UI for React with the Getting Started topic, then import the IgrVirtualScroll and give it an item template and data. The React wrapper registers the underlying element when the module loads, so no registration call is needed:

import { IgrVirtualScroll } from 'igniteui-react';
import type { VirtualScrollItemContext } from 'igniteui-react';

const items = Array.from({ length: 100_000 }, (_, i) => ({ name: `Item ${i}` }));

export function Employees() {
    return (
        <IgrVirtualScroll
            data={items}
            itemTemplate={(ctx: VirtualScrollItemContext<Item>) => (
                <div className="row">{ctx.index}: {ctx.value.name}</div>
            )}
            style={{ height: '400px' }}
        />
    );
}

The Virtual Scroll host needs a fixed height for vertical scrolling or a fixed width for horizontal scrolling. A host that grows with its content renders every item, so the list is not virtualized.

Prerequisites and Version Compatibility

Requirement Value
Package igniteui-react (MIT)
First release with the component 19.9.0
Peer packages react and react-dom 18 or 19
Item templates JSX returned from a function. igniteui-webcomponents comes in as a dependency of igniteui-react; you do not install it yourself.
Theme Import a theme stylesheet once, for example igniteui-webcomponents/themes/light/bootstrap.css.

Usage

Item Template

The Virtual Scroll item template receives the item and its position in the whole collection. Use the index and the total count for position-dependent content, such as alternating styles or aria-posinset and aria-setsize.

Set itemTemplate to a function that returns JSX. The function receives a VirtualScrollItemContext with value (the item), index, count, isFirst, and isLast. Without an item template, the component renders nothing.

<IgrVirtualScroll
    data={employees}
    estimatedItemSize={64}
    itemTemplate={(ctx: VirtualScrollItemContext<Employee>) => (
        <IgrListItem ariaPosinset={ctx.index + 1} ariaSetsize={ctx.count}>
            <IgrAvatar slot="start" shape="circle" initials={ctx.value.initials} />
            <span slot="title">{ctx.value.name}</span>
            <span slot="subtitle">{ctx.value.email}</span>
        </IgrListItem>
    )}
    style={{ height: '480px' }}
/>

Data

The Virtual Scroll data collection is compared by reference. Assign a new array to update the list; changing the bound array in place, for example with push, does not update it.

setEmployees(current => [...current, newEmployee]);

When data changes, the component keeps the measured sizes of the items before the first changed index and measures the rest again when they render. Appending keeps every existing measurement; replacing, filtering, or sorting discards the measurements from the first changed item onwards.

An item keeps its element while its key is in the rendered window. Without keyFunction the index is the key, so after a sort, an insert, or a removal the element at an index stays put and shows its new item. Return a stable id from keyFunction when items move within data:

virtualScroll.keyFunction = (employee) => employee.id;

Estimated Item Size

The Virtual Scroll estimatedItemSize is the size in pixels an item has until it renders and is measured (50 by default). Items can have different sizes: each measured size replaces the estimate.

Set the estimate close to the average item size so that the first render lands near the real content. Once items are measured, their average replaces the estimate for the items that are not measured yet, so the scrollbar and scrollToIndex correct themselves even when the estimate is off.

<IgrVirtualScroll data={employees} estimatedItemSize={80} style={{ height: '480px' }} />

Items are measured by their border box, so margins are not part of an item’s size. Space items with padding, or with a gap inside the item, instead of margins.

Orientation

The Virtual Scroll orientation sets the scroll axis: vertical (default) or horizontal. In a horizontal list, give each item a width and the host a height. In a right-to-left context, horizontal scrolling and item positioning are mirrored.

<IgrVirtualScroll
    orientation="horizontal"
    data={employees}
    estimatedItemSize={220}
    itemTemplate={ctx => <div style={{ width: '220px' }}>...</div>}
    style={{ height: '200px' }}
/>

Over-Scan

The Virtual Scroll overScan is the number of extra items rendered beyond each edge of the viewport (2 by default). A larger value reduces blank areas during fast scrolling and renders more elements.

<IgrVirtualScroll data={items} overScan={6} style={{ height: '400px' }} />

Scroll to Index

The Virtual Scroll scrollToIndex method scrolls an item into view. Its options are those of the native scrollIntoView: block (start, center, end, or nearest), inline for a horizontal list, and behavior (auto or smooth). Items that have not rendered only have an estimated size, so the component measures the items where it lands and corrects the position; the returned promise resolves on the final position.

const virtualScroll = useRef<IgrVirtualScroll>(null);

async function goTo(index: number): Promise<void> {
    await virtualScroll.current?.scrollToIndex(index, { block: 'center' });
}

With block: 'nearest', the position does not change when the item is already fully visible. Indices outside the collection are clamped to the first or last item.

Infinite Scroll

The Virtual Scroll onDataRequest event supports append-only loading from remote data. It is emitted when the rendered window nears the end of data, and on the first render when the loaded items do not fill the viewport. Append the requested items as a new array:

<IgrVirtualScroll
    data={employees}
    estimatedItemSize={64}
    onDataRequest={async (event: CustomEvent<VirtualScrollDataRequest>) => {
        const { startIndex, count } = event.detail;
        const page = await fetchEmployees(startIndex, count);
        setEmployees(current => [...current, ...page]);
    }}
    style={{ height: '440px' }}
/>

Only one data request is pending at a time; the next one follows the next data change. An empty data emits no request, so load the first page yourself. When the source has no more items, stop appending: the component does not request the same start index again.

Layout Complete

The Virtual Scroll layoutComplete property is a promise that resolves when the current render, the measurements it triggers, and the renders they schedule are complete. Await it before you read rendered items after a data change, a scroll, or a resize.

setEmployees(await fetchEmployees());
await virtualScroll.current?.layoutComplete;

Do/Don’t

The Virtual Scroll usually works as the scroll container of a long list, keeping only the items in its viewport, plus a small buffer, in the DOM. Avoid it for a list short enough to render at once, and as you write the item template, keep each item state in the data rather than in its elements: item elements are reused, so DOM state the template does not bind shows on whichever item takes the element.

React Virtual Scroll showing a list of 100,000 employees
Do

Use the Virtual Scroll for a long list that is too large to render at once, such as a directory, a feed, a log, or a strip of cards, including lists that load remote data while scrolling.

React Virtual Scroll used for a list of only five employees
Don’t

Render a short list directly with the List. Use the React Data Grid for tabular data with columns, sorting, or filtering. Show a small set of rich items as Card elements without virtualization.

Properties

Name Type Default Description
data any[] [] The collection to virtualize. Compared by reference.
orientation 'vertical' | 'horizontal' 'vertical' The scroll axis.
overScan number 2 Extra items rendered beyond each edge of the viewport.
estimatedItemSize number 50 The size in pixels of an item until it is measured. A non-positive value uses 50.
itemTemplate (ctx: VirtualScrollItemContext) => ReactNode null The function that renders each item.
keyFunction (item, index) => unknown null Returns an item’s key, so an item keeps its element when it moves in data. Without it, the index is the key.
layoutComplete Promise<void> (read-only) — Resolves when rendering and item measurement have settled. Read it from a ref, not as a prop.

Methods

Name Returns Description
scrollToIndex(index: number, options?: ScrollIntoViewOptions) Promise<void> Scrolls the item at index into view and resolves when the corrected position is stable.

Events

Name Argument Description
onStateChange CustomEvent<VirtualScrollState> Emitted when the rendered window changes. event.detail carries startIndex, endIndex, viewportSize, totalSize.
onDataRequest CustomEvent<VirtualScrollDataRequest> Emitted when the rendered window nears the end of data. event.detail carries startIndex and count.

The handlers receive the native CustomEvent, so read the payload from event.detail.

Styling

The React Virtual Scroll has no theme of its own: it lays out the viewport, and the rendered items take their styles from the elements and components in the item template.

The component renders into its light DOM, so regular selectors reach the rendered items. Its default styles give the host a height of 18.75rem; override the height to size the viewport.

igc-virtual-scroll.employees {
    height: 480px;
}

igc-virtual-scroll.employees [data-vs-index]:nth-child(even) {
    background: var(--ig-gray-100);
}

Accessibility

The React Virtual Scroll keeps only the rendered window in the DOM, so the item template has to expose each item’s position in the whole collection.

Keyboard Interaction

The React Virtual Scroll adds no key handlers. The host is a native scroll container, and a focused scroll container scrolls with the browser’s keys:

Key Action
Arrow Up / Arrow Down Scrolls a vertical list.
Arrow Left / Arrow Right Scrolls a horizontal list.
Page Up / Page Down Scrolls by about one viewport.
Home / End Scrolls to the start or the end of the collection.

The host has no tabindex. Browsers differ in whether a scroll container without focusable content can receive focus, so set tabindex="0" on the host when the items contain nothing focusable. Focus inside an item does not survive that item leaving the rendered window, so move focus deliberately before it does.

Screen Readers / ARIA

  • The track, the content element, and the item wrappers have role="presentation". The host has no role: place it inside an element with a list role, such as igc-list, or give it role="list" when the items render role="listitem".
  • Map the index and count context properties to aria-posinset and aria-setsize.
  • Give a focusable host a role that supports an accessible name, such as role="list", and name it with aria-label or aria-labelledby.

Accessibility Compliance

Infragistics documents the accessibility standards that Ignite UI for React targets in the Accessibility Compliance topic. This topic makes no conformance claim for the Virtual Scroll: the table lists what the component provides, and the list after it covers what the application must add.

Criterion How the component supports the requirement
1.3.1 Info and Relationships The wrappers are presentational, so the list structure comes from the host and the item template, which can expose each item’s position with aria-posinset and aria-setsize.
2.1.1 Keyboard The host is a native scroll container that scrolls with the keyboard once it has focus. Reaching it with the keyboard depends on the application; see the list below.

Your responsibilities:

  • Make the host keyboard-reachable with tabindex="0" when the items contain nothing focusable, and give it an accessible name.
  • Expose the item position with aria-posinset and aria-setsize from the item template.
  • Provide list semantics that fit the item template (see Screen Readers / ARIA).
  • Keep application state, such as a selection, in the data rather than in the rendered item elements.

Troubleshooting

Why does the Virtual Scroll render no items?

The host has no size on the scroll axis, the item template is missing, or data is empty. Give the host a fixed height (vertical) or width (horizontal), set the item template, and check the bound collection.

Why does the list not update when I add an item?

The Virtual Scroll compares data by reference, so a change in place is not detected. Assign a new array, for example [...items, newItem].

Why does the scrollbar change size while I scroll?

Items that have not rendered use estimatedItemSize, and the total size is corrected as items are measured.

The average measured size then replaces the estimate for the items that are not measured yet, so the correction settles as you scroll. Set estimatedItemSize close to the average item size to make the first render land near the real content.

Why do items drift out of place further down the list?

Margins are not part of an item’s measured size. Replace item margins with padding, or with a gap inside the item.

Known Limitations

  • The React Virtual Scroll virtualizes one axis. Rows and columns that are both virtualized require a grid.
  • Item elements are reused as the window moves, so DOM state that the item template does not bind, such as a checkbox without a bound checked, shows on whichever item takes the element. Bind all item state, and write user changes back to the item.

API References

Dependencies

The React Virtual Scroll has no dependencies on other components and needs no theme of its own. igniteui-react brings in igniteui-webcomponents and lit, and the components in the item template need a theme stylesheet.

Additional Resources

  • List - Use the List for a short list, or as the container of a virtualized list.
  • Data Grid - Use the Data Grid for tabular data with columns, sorting, or filtering.
  • Card - Use cards for a small set of rich items, or as items of a horizontal Virtual Scroll.

FAQ

How many items can the Virtual Scroll handle?

The React Virtual Scroll keeps only the items in its viewport and the over-scan buffer in the DOM, so the size of the collection does not change how many elements render. When the total size of a collection exceeds the browser’s scroll limit, the Virtual Scroll maps the collection onto the scroll range the browser supports.

Do Virtual Scroll items need the same size?

Items in the React Virtual Scroll can have different sizes, because each item is measured once it renders. Set estimatedItemSize close to the average item size so that the scrollbar is accurate before items are measured.

Once items are measured, their average replaces the estimate for the items that are not measured yet.

How do I scroll the Virtual Scroll to a specific item?

Call the React Virtual Scroll scrollToIndex method with the item index and optional block and behavior options. The method returns a promise that resolves when the corrected position is stable.

How do I load remote data into the Virtual Scroll while the user scrolls?

The React Virtual Scroll supports append-only loading through the onDataRequest event. Handle the event and assign a new array that includes the requested items to data.