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)
scrollerRef · visible 400 px viewport
scrollTop ≈ 172,000 px · 43% through the list
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.
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.
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:
| Adapter | Viewport / spacer / window bindings |
|---|---|
| React and Preact | scrollerRef, sizeRef, itemsRef |
| Solid | scrollerRef, sizeRef, itemsRef |
| Vue | scrollerRef, sizeRef, itemsRef |
| Svelte | scroller, size, items attachments |
| Lit | refs on the same VirtualController that owns the model |
| Core | setters 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 = 0In the middle
transform: translateY(172000px)At the end
to = itemCountThe 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.