npm / @tanstack/virtual-core
@tanstack/virtual-core
What changed in @tanstack/virtual-core 3, from its official release notes, upgrade guides and advisories. Sources checked 2026-11-13.
4 breaking1 deprecations12 new APIs and options5 performance60 fixes and other
See which of these apply to your code, ranked, in your agent:
npx @unstale/cli init@tanstack/virtual-core 382 changes
Breaking changes 4
scrollAdjustments is now a read-only getter with the same value; assigning to it is no longer supported.
When lanes > 1, overscan counts whole rows, rendering the same number of extra items in every lane; custom rangeExtractors get row overscan only if they delegate to defaultRangeExtractor.
Changes to the gap option now invalidate measurements.
Reverts notifying the framework when the count changes.
Deprecations 1
Deprecated because corrections are folded into scrollOffset immediately; read scrollOffset alone. The getter remains and scrollAdjustments is always 0.
New APIs and options 12
Range gains an optional lanes field, which defaultRangeExtractor reads.
Adds a method to stop an in-flight scroll operation from correcting toward its target, allowing a user gesture to take over the viewport.
Adds an option to skip DOM measurement when the list is hidden; the default measureElement returns the cached size or estimateSize fallback instead of reading the DOM.
Adds end-anchored virtualization with anchorTo: 'end', keeping the visible item stable when older items are prepended and an end-pinned viewport pinned when the last item grows. Start-anchored behavior remains the default.
Adds an option to scroll new items into view only when the viewport was already at the end.
Adds a helper for chat-style integrations to get the distance from the end.
Adds a helper for chat-style integrations to determine whether the viewport is at the end.
Adds a scroll-end threshold for chat-style integrations.
Adds a helper for scrolling to the end in chat-style integrations.
Adds an instance method that returns currently measured items as VirtualItem objects, for restoring measurements through initialMeasurementsCache alongside the saved scrollOffset.
Adds the laneAssignmentMode option.
Adds instant to the ScrollBehavior type to match the W3C spec.
Performance 5
Multi-lane getMeasurements uses an incremental per-lane argmin for shortest-lane lookup, improving performance without changing placement output.
The default lanes === 1 path computes ranges without per-scroll-frame allocations, reducing GC pressure during continuous scrolling.
Redundant scroll events are skipped when the offset has not changed.
The default measureElement skips synchronous offsetWidth/offsetHeight reads when a cached size exists, reducing layout reflow on re-renders.
Skips synchronous DOM reads during normal scrolling.
Fixes and other changes 60
Computes offsets from current measurements rather than measurements from the last render; the first write of scrollToIndex also uses current measurements.
Fixes programmatic scrolling in horizontal RTL elements by converting logical offsets, including measurement adjustments, to negative DOM scroll offsets.
Keeps a full viewport of rows in the range when the scroll offset is past the end of the list, such as after the list shrinks and before the element reports its clamped position.
Avoids ResizeObserver loop errors by measuring new rows immediately, deferring their observation until the next frame, and stopping observation of detached rows before delivery ends.
A command issued during a touch fling now stops the fling on Android too.
Accounts for scrollMargin when deciding whether the viewport is at the end.
Holds a clamped compensation retry while a touch owns the scroll.
Cancels stale anchor work when an explicit scroll occurs.
Measures the scroll viewport using client dimensions so borders and scrollbars do not obscure end-aligned items.
Corrections made while disabled are no longer accumulated and carried over after re-enabling.
Commands now follow the scroll maximum through sizer changes and treat browser-clamped writes as their own, so commands can retarget and land instead of being cancelled or stopping short.
A target now moves with size corrections, prepends no longer overwrite commands with stale offsets, and an anchor is retained across a scroll event before adapter commit.
Corrections are deferred only for touch-driven scrolling on iOS, allowing programmatic landings to compensate before paint.
Corrections the element cannot currently apply are held, reflected in scrollOffset and the exposed rows and total size, then written when possible.
Changing the scroll element no longer carries over a pending iOS late-fling window that could defer corrections on the new element.
On iOS 27 and later, commands issued during a fling stop the fling and take effect at once; user takeover before the write still cancels the command.
When enabled, animation frames are requested from the scroll element's window and queued frames are canceled on cleanup, preventing lists in another window from stalling when the opener is minimized or occluded.
A second touchend or touchcancel inside the post-touch grace window restarts the window instead of allowing the first timer to end it early.
After the scroll element is swapped or the virtualizer disabled, writes to the old element are no longer matched to the new element's first scroll report.
Treats iOS touchcancel like touchend so interrupted gestures do not leave touch state stuck or keep scroll anchoring suppressed.
scrollBy is built on a just-written offset.
A user can end a non-smooth scrollToIndex before reconciliation sees it land; later measurement no longer pulls the user back to the old target.
Resizes measured before a scroll command's write is read back are preserved, preventing an upward scrollToIndex from jumping back and stalling.
A callback measures all entries before notifying, preventing a synchronous re-render from unmounting part of a row before it is measured.
iOS-deferred size corrections are no longer replayed after an in-flight scrollToIndex lands.
Doesn't cache a zero size when given a node that is not yet attached to the document.
For the last item with end alignment, avoids overshooting by paddingEnd and respects scrollPaddingEnd; use scrollToEnd() to include paddingEnd.
Keeps reconciliation active until pending ResizeObserver measurements can update the target, and respects external scrolling away from a reached target during settling.
Reads the current scroll offset when the fallback fires so measurement adjustments made since the last browser scroll event are not overwritten by stale state.
Keeps a travelling smooth scroll alive when content is prepended, allowing the animation to continue to its recomputed target.
Keeps an end-pinned virtualizer following appended items when older items are trimmed in the same update without an increase in item count, while preserving reading anchors for users scrolled away from the end.
Preserves item keys in the lazy measurement cache so a stable callback reading mutable data cannot change the identity of previously measured rows.
Retries a clamped end-anchored scroll compensation write after the sizer grows, recovering the bottom pin without yanking users when another scroll event occurs.
cleanup() now resets isScrolling and scrollDirection, preventing them from remaining stuck after the scroll element changes or is removed.
Connected measurement nodes with indexes outside the current item count are ignored.
When a resize compensation moves the scroll position, resizeItem now notifies synchronously so the transform commit lands in the same paint as the scroll write, preventing a one-frame viewport jump.
Re-measurements now compensate only items entirely above the fold, preventing viewport-spanning items that grow below the anchor point from moving the scroll position. First measurements and custom overrides are unchanged.
Absolute scroll commands invalidate pending deferred deltas because their targets use current measurements; relative scrollBy commands keep the deferral.
At the end clamp with anchorTo: 'end', stale negative compensation is dropped when the browser has already clamped to the correct bottom position; positive deltas still replay.
Clamp the tracked scrollOffset at 0 during end-anchor measurement compensation and re-anchoring in setOptions, preventing a phantom distance from end and wedged iOS deferred corrections.
cleanup() resets iOS gesture and deferral state so it does not leak across scroll element swaps.
A scroll event at the held offset after the virtualizer's own adjustment write no longer changes the direction, preserving above-viewport re-measure compensation.
The intended scroll adjustment is carried in scrollOffset so end-anchored streaming resize does not drift when the browser clamps the scrollTop write.
The default scroll-adjustment predicate now compensates scrollTop on the first measurement of an above-viewport item while scrolling backward, preventing items from jumping; it skips compensation only for re-measurements during backward scroll.
Eagerly adjusts scrollOffset during the render pass when items are prepended with anchorTo: 'end' and dynamic sizes, preventing a one-frame visible jump.
Cleanup of a disconnected node now finds its cache entry by node identity rather than calling getItemKey with a possibly stale index, preventing errors when items are removed while a ResizeObserver has the node queued.
The flush now rolls its accumulated delta into scrollAdjustments so a resize between the flush and resulting scroll event computes its next correction from the updated offset.
On iOS, scroll-position adjustments are deferred during touch, early momentum, and elastic overscroll; subpixel reconciliation and an overscroll clamp help prevent interrupted scrolling and snap-back jolts.
measure() resets pendingMin so a prior resizeItem() cannot preserve stale measurementsCache entries before that index; the next rebuild starts at 0.
Removes incorrect elementsCache cleanup using getItemKey.
Fixes smooth scrolling when item sizes are dynamic.
Returns early for disconnected nodes during element measurement.
Prevents a crash when a component unmounts during scrollToIndex by adding a null guard for targetWindow inside the requestAnimationFrame callback.
Preserves auto alignment for visible items when scrolling.
Aborts pending scroll operations when a new scrollToIndex is called.
Waits an extra frame for ResizeObserver measurements before verifying position, improving reliability in dynamic mode.
Scrolling to the last index now works properly.
Lane assignments are recalculated correctly when the lane count changes dynamically, preventing out-of-bounds errors and broken layouts.
When count changes, the virtualizer notifies the framework so getTotalSize() updates without manual useMemo workarounds.
Lane assignments are cached to prevent items from jumping between lanes when the viewport is resized.