How virtual layout works

A virtual list renders only a small window of items, but the browser must still see the height of the complete collection. Three nested elements split those jobs cleanly:

What the browser sees

10,000 rows × 40 px

sizeRef · 400,000 px total

itemsRef · translateY(172,000 px)

row 4,300
row 4,301
…nearby rows only…

scrollerRef · visible 400 px viewport

scrollTop ≈ 172,000 px · 43% through the list

1

Viewport

The visible viewport, often bound as scrollerRef. It owns scrollTop, receives scroll events, and is styled by core with overflow: auto and contain: strict.

2

Total-size spacer

The full native scroll extent, often bound as sizeRef. Core sets its height or width to model.scrollSize, so the scrollbar represents every item—even though most items are absent from the DOM.

3

Rendered window

The small rendered window, often bound as itemsRef. It contains only the current model range and core moves it to the right location with a compositor-backed transform.

The required nesting

The three elements form one invariant tree. Application headers and footers may sit beside the spacer, but the rendered window remains inside it:

viewport                            visible area and scroll owner
├── header (optional)
├── total-size spacer               complete native scroll extent
│   └── rendered window             only the current [from, to) range
└── footer (optional)
<div ref={scrollerRef}>
    <div ref={sizeRef}>
        <div ref={itemsRef}>{renderRange(model.from, model.to)}</div>
    </div>
</div>

The names differ by framework, but the roles do not:

AdapterViewport / spacer / window bindings
React and PreactscrollerRef, sizeRef, itemsRef
SolidscrollerRef, sizeRef, itemsRef
VuescrollerRef, sizeRef, itemsRef
Sveltescroller, size, items attachments
Litrefs on the same VirtualController that owns the model
Coresetters on VirtualScrollerLayout

See the nested-container implementations when these roles cannot be immediate parent and child elements.

The half-open rendered range starts at

model.from and ends before model.to.

What changes while scrolling

At the start

from = 0

In the middle

transform: translateY(172000px)

At the end

to = itemCount

The viewport stays the scroll owner and the spacer stays the complete extent. Usually only the range inside the rendered window and its transform change. Measurements may also refine the spacer when estimates are replaced by real sizes.

Ownership rule

Core owns the layout-critical inline styles of all three elements after their bindings attach. The framework owns which children exist inside the rendered window; the application still owns presentation such as the scroller’s dimensions, border, background, and classes.

This single-writer rule prevents a delayed framework commit from replacing newer geometry that core already published during scrolling.