-
Notifications
You must be signed in to change notification settings - Fork 10
Virtual scroll [Draft]
- Virtual Scroll Specification
Team Name: Design and Web Development
Developer name: Radoslav Karaivanov
Designer name: N/A
- Damyan Petev
- Radoslav Mirchev
- Damyan Petev
- Radoslav Mirchev
| Version | Author | Date | Description |
|---|---|---|---|
| 1 | Radoslav Karaivanov | 2026-06-01 | Initial draft |
igc-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, items that leave the viewport are destroyed and new items entering the viewport
are created. 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, fixed and variable
item sizes, and an infinite/remote-data loading pattern through its igcDataRequest event.
The igc-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 fixed and variable item sizes.
- measure each rendered item and replace its estimated size with the actual measured size.
- emit
igcStateChangeafter each render with the current visible-window state. - emit
igcDataRequestwhen the scroll position approaches the end of the loaded data, enabling infinite/remote-scroll patterns. - expose a
scrollToIndexmethod for programmatic navigation. - operate as a Light DOM component, integrating transparently into the document or any shadow root without style leakage.
- pass accessibility audits.
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.
As a developer I expect to be able to:
- provide a data array of any type and a template function 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
igcStateChangeto know which items are currently visible. - subscribe to
igcDataRequestto 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
igc-virtual-scrollinside other components, including shadow roots, without style bleed-through.
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: items that leave the viewport are
removed from the DOM and new items entering the viewport are created. The over-scan buffer
(over-scan attribute) controls how many extra items outside the visible area remain
rendered to prevent blank flashes during fast scrolling.
Developers provide two things to the component:
- A
dataarray of items. - An
itemTemplatefunction that receives aVirtualScrollItemContext<T>and returns a LitTemplateResult.
const template = (ctx: VirtualScrollItemContext<Person>) => html`
<div class="item">${ctx.index}: ${ctx.value.name}</div>
`;
scroll.data = people;
scroll.itemTemplate = template;VirtualScrollItemContext<T> properties available inside the template:
| Property | Type | Description |
|---|---|---|
value |
T |
The current data item. |
index |
number |
The absolute index within the full data array. |
count |
number |
The total number of items in data. |
isFirst |
boolean |
true when index === 0. |
isLast |
boolean |
true when index === count - 1. |
Each rendered item is wrapped in a <div data-vs-index="N"> 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 wrappers
with a ResizeObserver. When an item's actual size differs from its current estimated size,
the engine is updated and a re-render is scheduled. This makes the layout self-correcting
for variable-height or variable-width content with no additional configuration.
Infinite scroll: When range.endIndex approaches within 5 items of the end of the
loaded data, the igcDataRequest event is emitted exactly once per data batch. The
component suppresses further emissions until the consumer responds with a new data
reference.
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.
None applicable.
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.
| Property | Attribute | Reflected | Type | Default | Description |
|---|---|---|---|---|---|
data |
- | No | T[] |
[] |
The array of items to virtualize. Assigning a new array triggers an engine resize and resets the pending data-request flag. |
orientation |
orientation |
Yes | 'vertical' | 'horizontal' |
'vertical' |
Scroll axis. 'vertical' scrolls on the block axis; 'horizontal' scrolls on the inline axis. |
overScan |
over-scan |
No | 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 |
estimated-item-size |
No | 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 retain their measured sizes. |
itemTemplate |
- | No | VirtualScrollItemTemplate<T> | null |
null |
A function (ctx: VirtualScrollItemContext<T>) => TemplateResult | nothing that renders each item. If null, the component renders nothing. |
| Method | Signature | Description |
|---|---|---|
scrollToIndex |
(index: number, options?: ScrollToOptions): void |
Programmatically scrolls the container so that the item at index is at the leading edge of the viewport. Accepts the standard ScrollToOptions (e.g. { behavior: 'smooth' }). |
| Event | Type | Description |
|---|---|---|
igcStateChange |
CustomEvent<VirtualScrollState> |
Emitted after each render pass with a snapshot of the current visible window. Fires only when at least one item is rendered (endIndex >= startIndex). |
igcDataRequest |
CustomEvent<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 starting at this index. |
count |
number |
Suggested number of items to load (max(overScan x 4, 20)). |
None. The component renders into its own Light DOM; items are created directly by the
itemTemplate function without slots.
None. The component does not have a Shadow Root.
None. Default host styles are injected via the Constructable Stylesheet API
(adoptedStyleSheets) and do not expose CSS custom properties. Consumer applications
style the igc-virtual-scroll host selector and their item template content directly.
- Verify that the component renders and passes the accessibility audit (
lightDom.to.be.accessible()). - Verify that default property values are correct (
data: [],orientation: 'vertical',overScan: 2,estimatedItemSize: 50,itemTemplate: null). - Verify that the
orientationattribute is reflected on the host element. - Verify that setting
dataanditemTemplaterenders the track ([part="igc-vs-track"]) and content ([part="igc-vs-content"]) elements. - Verify that setting only
datawithout anitemTemplaterenders nothing. - Verify that the track height/width equals
data.length x estimatedItemSizeon initial render (before measurement). - Verify that updating
datawith a larger array resizes the track element accordingly. - Verify that
igcStateChangeis emitted after render when at least one item is visible. - Verify that
igcDataRequestis emitted whenendIndexis within 5 items ofdata.length. - Verify that
igcDataRequestis not emitted again untildatais updated with a new reference. - Verify that
scrollToIndexsetsscrollTopfor vertical orientation. - Verify that
scrollToIndexsetsscrollLeftfor horizontal orientation.
igc-virtual-scroll is a transparent scroll container. All semantic roles and ARIA
attributes are the responsibility of the itemTemplate content provided by the developer.
The internal track and content wrapper elements are presentational and do not need to be exposed to assistive technologies; any content rendered in the Light DOM inherits the document's accessibility tree naturally.
The host element is a display: block; overflow: auto container with no intrinsic ARIA
role. It does not manage focus internally.
-
ARIA:
feedrole - a recommended pattern for infinite-scroll containers requiring keyboard navigation. - ResizeObserver API
- CSS
containproperty - Constructable Stylesheets
Horizontal orientation works correctly in RTL contexts. The component reads scrollLeft,
which browsers normalize for RTL layouts. No additional configuration is required.
- The
itemTemplatefunction is invoked once per rendered item per render cycle. Expensive template computation should be memoized by the consumer. - The
dataarray is treated as an immutable snapshot. Mutating the array in place without reassigning thedataproperty will not trigger a re-render. - Changing
estimatedItemSizeafter data is loaded does not retroactively update items that have already been measured; it applies only to new items added by a subsequentdataresize. - 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
feedrole pattern on top of this component. - The
dataitems are used as positional render slots; the component does not perform identity-keyed diffing (norepeatdirective). Reordering items within the visible window is handled by in-place patching, not DOM node movement.