Skip to content

Virtual scroll [Draft]

Radoslav Karaivanov edited this page Jun 11, 2026 · 1 revision

Virtual Scroll Specification

Owned By

Team Name: Design and Web Development

Developer name: Radoslav Karaivanov

Designer name: N/A

Requires approval from:

  • N/A

Signed off by:

  • N/A

Revision history

Version Author Date Description
1 Radoslav Karaivanov 2026-06-11 Initial draft

Overview

igx-virtual-scroll efficiently renders large lists by keeping only the items currently visible in the viewport — plus an optional over-scan buffer — in the DOM. As the user scrolls, embedded views that leave the viewport are detached and pooled, and views entering the viewport are reused from the pool (or created on demand) and patched with the new item context. A track spacer element maintains the full virtual scroll height/width so the browser scrollbar accurately represents the complete dataset size.

The component supports both vertical and horizontal scroll orientations (including right-to-left layouts), fixed and variable item sizes, and an infinite/remote-data loading pattern through its dataRequest output.

Acceptance criteria

The igx-virtual-scroll must:

  • render only the items currently visible in the viewport plus the over-scan buffer.
  • maintain a correct scrollbar representation of the full virtual content size.
  • support vertical and horizontal scroll orientations.
  • support horizontal scrolling in right-to-left (RTL) layouts.
  • support fixed and variable item sizes.
  • measure each rendered item and replace its estimated size with the actual measured size.
  • emit stateChange after each render with the current visible-window state.
  • emit dataRequest when the scroll position approaches the end of the loaded data, enabling infinite/remote-scroll patterns.
  • expose a scrollToIndex method for programmatic navigation.
  • reuse embedded views via a detach/pool strategy to minimize DOM churn during steady-state scrolling.
  • pass accessibility audits.

User stories

End-user stories

As an end-user I expect to:

  • scroll through a large list smoothly, with no visible blank flickers.
  • see the scroll bar accurately reflect my position within the full dataset.
  • have items rendered at their actual size, not a uniform estimated size.
  • scroll horizontally in right-to-left layouts with the content mirrored correctly.

Developer stories

As a developer I expect to be able to:

  • provide a data array of any type and a template to render each item.
  • choose between vertical and horizontal scroll orientations.
  • control how many extra items are rendered beyond the visible area to tune scroll smoothness versus DOM overhead.
  • provide an estimated item size to improve initial layout before actual sizes are measured.
  • subscribe to stateChange to know which items are currently visible.
  • subscribe to dataRequest to append more data when the user scrolls near the end (infinite scroll / remote paging).
  • programmatically scroll the component to a specific item index using scrollToIndex.
  • nest igx-virtual-scroll inside other components without style bleed-through.

Functionality

End-user experience

The component renders as a scrollable container. Items are laid out inside an absolutely-positioned content wrapper that is translated (via CSS transform) to match the current scroll position. A track spacer element expands to the full virtual size to create the correct scroll range for the browser.

When the user scrolls, the visible window shifts: views that leave the viewport are detached and returned to a reuse pool, and views entering the viewport are re-inserted and patched. The over-scan buffer (overScan input) controls how many extra items outside the visible area remain rendered to prevent blank flashes during fast scrolling.

Developer experience

Developers provide two things to the component:

  1. A data array of items.
  2. An item template, supplied either as a content ng-template decorated with the igxVirtualItem directive, or programmatically through the itemTemplate input. When both are present, itemTemplate takes precedence.
<igx-virtual-scroll [data]="people" style="height: 400px;">
  <ng-template igxVirtualItem let-person let-i="index">
    <div class="item">{{ i }}: {{ person.name }}</div>
  </ng-template>
</igx-virtual-scroll>
@Component({ /* ... */ })
export class MyComponent {
  protected readonly people = signal(
    Array.from({ length: 10_000 }, (_, i) => ({ name: `Item ${i}` })),
  );
}

IgxVsItemContext<T> properties available inside the template:

Property Type Description
$implicit (or let-item) T The current data item.
index number The absolute index within the full data.
count number The total number of items in data.
first boolean true when index === 0.
last boolean true when index === count - 1.
even boolean true when index is even.
odd boolean true when index is odd.

Each rendered item's root element is tagged with a data-vs-index="N" attribute, which the component uses internally to associate ResizeObserver measurements back to the correct index in the engine.

Size measurement: After each render the component observes all rendered item roots with a ResizeObserver. When an item's actual size differs from its current estimated size, the engine is updated and the dependent signals (_visibleRange, _spaceSize, _contentTransform) recompute. This makes the layout self-correcting for variable-height or variable-width content with no additional configuration.

Infinite scroll: When the visible endIndex approaches within 5 items of the end of the loaded data, the dataRequest event is emitted exactly once per data batch. A _hasPendingDataRequest flag suppresses further emissions until the consumer responds with a new data reference. The event is also suppressed on the initial render (before the user has scrolled).

Coordinate compression: For extremely large datasets where the virtual total size exceeds the browser's maximum scroll coordinate (~33.5 million px in Chrome), the component transparently maps virtual positions to DOM scroll positions using a virtual ratio. Developers do not need to account for this.

Localization

None applicable.

Keyboard navigation

The component itself is not focusable. Keyboard scrolling (arrow keys, Page Up/Down, Home, End) operates natively on the scrollable host element as with any overflow: auto container. Focus management within the rendered list items is the responsibility of the itemTemplate content.


API

Inputs

Input Type Default Description
data T[] [] The array of items to virtualize. Assigning a new array triggers an engine resize and resets the pending data-request flag.
orientation 'vertical' | 'horizontal' 'vertical' Scroll axis. 'vertical' scrolls on the block axis; 'horizontal' scrolls on the inline axis.
overScan number 2 Number of extra items to render beyond the visible area on each side. Higher values reduce blank flashes at the cost of additional DOM nodes.
estimatedItemSize number 50 Estimated item size in pixels used for initial layout before items are measured in the DOM. Applies only to newly-added items; already-measured items keep sizes.
itemTemplate TemplateRef<IgxVsItemContext<T>> | null null A template that renders each item, taking precedence over a content ng-template[igxVirtualItem]. If neither is provided, the component renders nothing.

Methods

Method Signature Description
scrollToIndex (index: number): void Programmatically scrolls the container so that the item at index is at the leading edge of the viewport. Mirrored for RTL layouts.

Outputs

Output Type Description
stateChange EventEmitter<VirtualScrollState> Emitted after each render pass with a snapshot of the current visible window. Fires only when at least one item is rendered.
dataRequest EventEmitter<VirtualScrollDataRequest> Emitted once when the visible range comes within 5 items of data.length. Suppressed until a new data reference is assigned.

VirtualScrollState

Property Type Description
startIndex number Index of the first rendered item (inclusive).
endIndex number Index of the last rendered item (inclusive).
viewportSize number Current size of the scrollable viewport in px.
totalSize number Total virtual size of all items in px.

VirtualScrollDataRequest

Property Type Description
startIndex number The first index for which data is not yet available. Append at least count items from here.
count number Suggested number of items to load (max(overScan × 4, 20)).

Content

Items are projected through a single item template. The template is resolved in the following order of precedence:

  1. The itemTemplate input, when set.
  2. A content ng-template decorated with the igxVirtualItem (IgxVirtualItemDirective) directive.

If neither is provided, the component renders nothing.

CSS classes

Class Applied to Description
igx-virtual-scroll host Base host class.
igx-virtual-scroll--vertical host Present when orientation is vertical.
igx-virtual-scroll--horizontal host Present when orientation is horizontal.
igx-vs__track element Spacer element sized to the full virtual size.
igx-vs__content element Absolutely-positioned, translated content wrapper.

CSS variables

None. The component ships structural styles only and does not expose CSS custom properties. Consumer applications style the igx-virtual-scroll host selector and their item template content directly.


Test scenarios

Automation

  • Verify that the component renders and exposes role="list" and the igx-virtual-scroll class on the host.
  • Verify default input values (data: [], orientation: 'vertical', overScan: 2, estimatedItemSize: 50, itemTemplate: null).
  • Verify that the vertical/horizontal modifier class is applied based on orientation.
  • Verify that setting data and providing a template renders the track (.igx-vs__track) and content (.igx-vs__content) elements.
  • Verify that the component renders only a subset of items (not the whole dataset).
  • Verify that providing only data without any template renders nothing.
  • Verify that the track height/width reflects the virtual size on initial render.
  • Verify that updating data with a larger array resizes the track element.
  • Verify that stateChange is emitted after render when at least one item is visible, with startIndex, endIndex, viewportSize, and totalSize.
  • Verify that dataRequest is emitted when endIndex is within 5 items of data.length and the user has scrolled.
  • Verify that dataRequest is not emitted on the initial render.
  • Verify that dataRequest is not emitted again until data is updated with a new reference.
  • Verify that scrollToIndex sets scrollTop for vertical orientation.
  • Verify that scrollToIndex sets scrollLeft for horizontal orientation.
  • Verify that scrollToIndex sets a negative scrollLeft for RTL horizontal orientation.
  • Verify that the content wrapper is translated in the negative direction for RTL horizontal orientation.
  • Verify that IgxVirtualItemDirective is picked up as a content child and exposes a non-null TemplateRef.

Accessibility

igx-virtual-scroll is primarily a scroll container. The host element carries role="list"; the semantics of each rendered item are the responsibility of the itemTemplate content provided by the developer.

The internal track and content wrapper elements are presentational (aria-hidden="true" on the track) and are not exposed to assistive technologies; any content rendered into the item template inherits the document's accessibility tree naturally.

The host is a display: block; overflow: auto container. It does not manage focus internally.

References

RTL

Horizontal orientation works correctly in right-to-left contexts. The component detects the host's resolved direction via the isLeftToRight utility and:

  • normalizes the browser's negative scrollLeft in RTL into a positive virtual scroll position, keeping all engine math direction-agnostic;
  • applies the content wrapper's translateX offset in the negative (leading) direction;
  • negates the scrollLeft target in scrollToIndex.

The content wrapper is additionally re-anchored to the right edge of the track via the :host(.igx-virtual-scroll--horizontal:dir(rtl)) style rule. No additional configuration is required from the consumer.


Assumptions and limitations

  • The item template is invoked once per rendered item per render cycle. Expensive template computation should be memoized by the consumer.
  • The data array is treated as an immutable snapshot. Mutating the array in place without reassigning the data input will not trigger a re-render.
  • Changing estimatedItemSize after data is loaded does not retroactively update items that have already been measured; it applies only to new items added by a subsequent data resize.
  • Items are virtualized in a single flat sequence. Multi-column grid layouts are not natively supported; consumers must manage column layout within the item template.
  • The component does not implement programmatic focus management within the virtual list. Consumers requiring a fully keyboard-navigable infinite list should implement the ARIA feed role pattern on top of this component.
  • The data items are used as positional render slots; the component does not perform identity-keyed diffing. Reordering items within the visible window is handled by in-place context patching, not DOM node movement.

Clone this wiki locally