@tanstack/virtual-core
- 3.19.5 is the latest, published 15 October 2026.
- No known security advisory.
- 3.x: 83 releases since 1 December 2023, 14 in the last 90 days.
Releases in the last 90 days
Feed of releases and advisoriesEvery stable release, newest first. Notes are quoted as the maintainers published them.
3.19.5
15 October 2026virtual-core: keep an instant scroll command clamped at a max the sizer committed outside a row resizeBefore an instant scroll command's write is reported, the browser can clamp it at a smaller scroll max that the sizer moves to without a row resize: an append or prepend, measure(), or a resize the sizer had not committed when the command was written. The virtualizer now also records the max at each commit, so such an event is read as its own clamped write and the command lands. A clamped write reported with isScrolling false is now written again too.
Patch Changes
fix(virtual-core): keep an instant scroll command clamped at a max the sizer committed outside a row resize
Before an instant scroll command's write is reported, the browser can clamp it at a smaller scroll max that the sizer moves to without a row resize: an append or prepend,
measure(), or a resize the sizer had not committed when the command was written. The virtualizer now also records the max at each commit, so such an event is read as its own clamped write and the command lands. A clamped write reported withisScrollingfalse is now written again too.fix(virtual-core): keep an instant scroll command when the browser clamps its write before rows grow back
Rows measured right after an instant
scrollToEnd()(orscrollToIndex) can first shrink the sizer below the written position and then grow it again in the same task. The browser clamps the write to the smaller max, and the scroll event reports that position. It was read as the user scrolling away, so the command was cancelled and the list stopped short of the end (a chat opened withestimateSizelarger than its rows). The virtualizer now follows the scroll max through those measurements and reads such an event as its own clamped write, so the command retargets and lands at the end.fix(virtual-core): scroll commands and corrections that stalled or were lost
On iOS 27 and later a
scrollTopwrite no longer stops a fling but is overridden by it within a frame. AscrollToIndex,scrollToOffsetorscrollToEndissued during a fling now waits for the fling to end instead of being lost. The user taking over before then cancels it: a drag, a second flick, or a finger stopping the fling (iOS reports none of these with touch events while the fling runs). Corrections keep being held through the gesture as on every version.scrollToOffset/scrollBy: a row above the viewport growing after the write's read-back moved the element off the fixed target, and the command kept reconciling until the 5s safety valve. The target now moves with the correction.A prepend (
anchorTo: 'end') before the read-back of a scroll command was anchored against the stale offset and written over the command. It is now left to reconcile, like a resize in that window.A scroll event between the prepend's options change and the adapter's commit no longer loses the anchor (one blank frame when the element could not be written by commit time).
A command's write clamped by a sizer that shrank below it before the read-back no longer reads as a user scroll; the command reaches its fresh target.
fix(virtual-core): on iOS 27 and later, a scroll command issued during a fling takes effect at once
A
scrollToIndex,scrollToOffsetorscrollToEndissued during a fling waited for the fling to end, so a button outside the list (such as "jump to latest") did nothing until the list stopped. The command now stops the fling by hiding the scroll element's overflow for two frames, then restores the element's own inline value and writes. The user taking over before the write still cancels the command.refactor(virtual-core): move the iOS touch gesture tracking into its own internal module. One fix along the way: changing the scroll element no longer carries over a pending iOS late-fling window, which could defer corrections on the new element for 150ms.
fix(virtual-core): on iOS, defer scroll corrections only for touch-driven scrolls (a finger down, or the momentum after it), so a programmatic landing like
scrollToOffsetcompensates before paint instead of snapping laterfix(virtual-core): hold a scroll correction the element cannot take instead of writing it
A prepend's anchor delta or an above-viewport size change that lands while a
scrollTopwrite is impossible (iOS during a touch gesture or its momentum tail, any WebKit while the element is rubber-banding past an edge) is now held:scrollOffsetcarries the corrected position, the rows and the total size are exposed shifted back by the held amount so every adapter keeps the reader's row where it is, and one write moves the element once a write is possible. Before, the write was discarded during the bounce on desktop Safari (the bounce stalled with the new rows and a blank top on screen), and on iOS the new rows showed through the bounce and snapped back at the flush.refactor(virtual-core): simplify the scroll-correction state (no behaviour change)
A scroll event is classified once (spurious re-emit, our own write's read-back, a clamped read-back or echo, or external) before the handler acts on it, a scroll command waiting for the iOS 27 momentum tail is a phase of its own, and the iOS touch gesture tracks a single
movedflag.scrollAdjustmentsis deprecated: a correction is folded intoscrollOffsetat once, so it is always 0. ReadscrollOffsetalone; the getter stays.One edge case changes: corrections made while
enabled: falseused to accumulate, and a correction made after re-enabling but before the next update was added on top of them. They are no longer carried over.
3.19.4
13 October 2026virtual-core: with useAnimationFrameWithResizeObserver, request frames from the scroll element's window instead of the global one, and cancel frames still queued on cleanup, so a list rendered into another window does not stall while the opener is minimized or occluded
Patch Changes
fix(virtual-core): with
useAnimationFrameWithResizeObserver, request frames from the scroll element's window instead of the global one, and cancel frames still queued on cleanup, so a list rendered into another window does not stall while the opener is minimized or occluded
3.19.3
13 October 2026virtual-core: simplify the scroll-correction state: a scroll write is tracked until its read-back as one record, the iOS touch gesture as one record, the pending prepend anchor as a tagged record, and every scroll command starts through one entry point (no behaviour change, with two exceptions: a second touchend/touchcancel inside the post-touch grace window now restarts the window instead of leaving the first timer to end it early, and after the scroll element is swapped or the virtualizer disabled, a write to the old element is no longer matched to the new element's first scroll report). scrollAdjustments becomes a read-only getter with the same value (assigning to it is no longer supported).
Patch Changes
refactor(virtual-core): simplify the scroll-correction state: a scroll write is tracked until its read-back as one record, the iOS touch gesture as one record, the pending prepend anchor as a tagged record, and every scroll command starts through one entry point (no behaviour change, with two exceptions: a second
touchend/touchcancelinside the post-touch grace window now restarts the window instead of leaving the first timer to end it early, and after the scroll element is swapped or the virtualizer disabled, a write to the old element is no longer matched to the new element's first scroll report).scrollAdjustmentsbecomes a read-only getter with the same value (assigning to it is no longer supported).
3.19.2
12 October 2026virtual-core: treat iOS touchcancel like touchend so interrupted gestures cannot leave touch state stuck and keep scroll anchoring suppressed
Patch Changes
fix(virtual-core): treat iOS
touchcancelliketouchendso interrupted gestures cannot leave touch state stuck and keep scroll anchoring suppressedrefactor(virtual-core): track an in-flight scroll command's lifecycle as one phase instead of several flags (no behaviour change)
3.19.1
12 October 2026virtual-core: let a user scroll end a non-smooth scrollToIndex before reconcile has seen it land, so a later measurement no longer pulls the user back to the old target
Patch Changes
fix(virtual-core): let a user scroll end a non-smooth
scrollToIndexbefore reconcile has seen it land, so a later measurement no longer pulls the user back to the old targetfix(virtual-core): leave resizes measured before a scroll command's write is read back to reconcile, so an upward
scrollToIndexno longer jumps back and stalls until the safety valve, and buildscrollByon a just-written offset
3.19.0
11 October 2026virtual-core: keep lanes aligned after changing lanes with measureElement
Minor Changes
fix(virtual-core): keep lanes aligned after changing
laneswithmeasureElementoverscannow counts whole rows whenlanes > 1, so every lane renders (and measures) the same number of extra items. This renders more items than before:lanes: 4, overscan: 2now adds 8 items on each side instead of 2.Rangegains an optionallanesfield thatdefaultRangeExtractorreads; a customrangeExtractorgets row overscan only if it delegates todefaultRangeExtractor.A ResizeObserver callback now measures all of its entries before notifying, so a synchronous re-render can no longer unmount part of a row before it is measured.
Patch Changes
fix(virtual-core): don't replay iOS-deferred size corrections after an in-flight
scrollToIndexlands
3.18.0
9 October 2026virtual-core: add cancelScroll() to stop an in-flight scrollToIndex / scrollToOffset / scrollBy / scrollToEnd from correcting toward its target, so a user gesture can take over the viewport
Minor Changes
feat(virtual-core): add
cancelScroll()to stop an in-flightscrollToIndex/scrollToOffset/scrollBy/scrollToEndfrom correcting toward its target, so a user gesture can take over the viewport
Patch Changes
Fix
scrollToIndex(last, { align: 'end' })overshooting the last item bypaddingEnd; it now respectsscrollPaddingEndlike other end-aligned items. To scroll to the very bottom includingpaddingEnd, usescrollToEnd().fix(virtual-core): don't cache a 0 size when
measureElementreceives a node that isn't attached to the document yetKeep scrollToIndex reconciliation active until pending ResizeObserver measurements can update its target, including when useAnimationFrameWithResizeObserver is enabled. Respect an external scroll away from a reached target during this settling period.
3.17.11
14 September 2026Read the current scroll offset when the debounced scroll-end fallback fires so measurement adjustments made since the last browser scroll event are not overwritten by stale state.
Patch Changes
Read the current scroll offset when the debounced scroll-end fallback fires so measurement adjustments made since the last browser scroll event are not overwritten by stale state.
Keep a travelling smooth
scrollToIndexalive when content is prepended. WithanchorTo: 'end', the prepend anchor sync wrotescrollTopinstantly, which cancelled the browser's smooth animation and left the scroll stranded partway; Chromium drops a smooth request re-issued right after such a cancel, so it could not be resumed. The sync is now skipped while a smooth programmatic scroll is still in flight, and the animation continues to its recomputed target. A smooth scroll that has already landed still receives the anchor sync.
3.17.10
11 September 2026Keep an end-pinned virtualizer following appended items when older items are trimmed in the same update and the item count does not increase. Recognize ordered, overlapping windows while preserving reading anchors for users who have scrolled away from the end.Preserve item keys in the lazy measurement cache so a stable getItemKey callback reading mutable data cannot change the identity of previously measured rows.
Patch Changes
Keep an end-pinned virtualizer following appended items when older items are trimmed in the same update and the item count does not increase. Recognize ordered, overlapping windows while preserving reading anchors for users who have scrolled away from the end.
Preserve item keys in the lazy measurement cache so a stable
getItemKeycallback reading mutable data cannot change the identity of previously measured rows.
3.17.9
7 September 2026Recover the bottom pin when the browser clamps an end-anchored scroll compensation write. resizeItem compensates a size change by writing scrollTop before the consumer has committed the new total size, so when the grown item does not itself extend the scroll range the browser clamps the write to the old maximum and the viewport is left short of the end with no scroll event to correct it. Two cases hit this: paddingEnd > 0 with a growing last item, where the overflowing item only extends scrollHeight to its own end and the clamp lands exactly paddingEnd short (#1258); and a row above the last one growing while the last row keeps its size, under directDomUpdates (#1266). A compensation write whose target exceeds the scroll maximum at write time is now recorded as clamped and re-issued once the sizer has grown — right after notify for consumers that size the container synchronously in onChange, and from _willUpdate for consumers that size it during a render. The clamped read-back keeps the retry pending; any other scroll event cancels it, so a user reading history is never yanked.
Patch Changes
Recover the bottom pin when the browser clamps an end-anchored scroll compensation write.
resizeItemcompensates a size change by writingscrollTopbefore the consumer has committed the new total size, so when the grown item does not itself extend the scroll range the browser clamps the write to the old maximum and the viewport is left short of the end with no scroll event to correct it. Two cases hit this:paddingEnd > 0with a growing last item, where the overflowing item only extendsscrollHeightto its own end and the clamp lands exactlypaddingEndshort (#1258); and a row above the last one growing while the last row keeps its size, underdirectDomUpdates(#1266). A compensation write whose target exceeds the scroll maximum at write time is now recorded as clamped and re-issued once the sizer has grown — right afternotifyfor consumers that size the container synchronously inonChange, and from_willUpdatefor consumers that size it during a render. The clamped read-back keeps the retry pending; any other scroll event cancels it, so a user reading history is never yanked.
Show 4 earlier releases
3.17.8
18 August 2026Cancel the pending isScrolling reset when a scroll observer is torn down, and reset isScrolling and scrollDirection in cleanup() so they don't stay stuck after the scroll element changes or is removed.
Patch Changes
Cancel the pending
isScrollingreset when a scroll observer is torn down, and resetisScrollingandscrollDirectionincleanup()so they don't stay stuck after the scroll element changes or is removed.Ignore connected measurement nodes whose indexes are outside the current item count.
3.17.7
28 July 2026Fix a one-frame viewport jump when above-viewport rows resize while scrolling up (#1227). resizeItem writes scrollTop synchronously inside the ResizeObserver callback to compensate for the size change, but then notified asynchronously — so the browser could paint a frame with the new scrollTop and the old item transforms, making the content jerk by the resize delta and snap back. When a compensation actually moves the scroll position, resizeItem now notifies synchronously so the transform commit lands in the same paint as the scroll write. Resizes that don't move the scroll position (below-fold measurements, iOS-deferred adjustments) keep the cheaper async notify.
Patch Changes
Fix a one-frame viewport jump when above-viewport rows resize while scrolling up (#1227).
resizeItemwritesscrollTopsynchronously inside the ResizeObserver callback to compensate for the size change, but then notified asynchronously — so the browser could paint a frame with the newscrollTopand the old item transforms, making the content jerk by the resize delta and snap back. When a compensation actually moves the scroll position,resizeItemnow notifies synchronously so the transform commit lands in the same paint as the scroll write. Resizes that don't move the scroll position (below-fold measurements, iOS-deferred adjustments) keep the cheaper async notify.
3.17.6
22 July 2026Stop the default scroll-adjustment heuristic from drifting the viewport when a viewport-spanning item grows. Previously any item whose top sat above the fold (itemStart < scrollOffset) had its size delta compensated on every re-measure — including a streaming chat message that spans the fold and grows at its bottom, dragging scrollTop downward token by token (#1218). Re-measurements now only compensate items that are entirely above the fold (itemStart + itemSize <= scrollOffset); growth below the anchor point leaves the scroll position untouched. First measurements (estimate→actual) still compensate any above-fold item, and a custom shouldAdjustScrollPositionOnItemSizeChange still overrides the default.
Patch Changes
Stop the default scroll-adjustment heuristic from drifting the viewport when a viewport-spanning item grows. Previously any item whose top sat above the fold (
itemStart < scrollOffset) had its size delta compensated on every re-measure — including a streaming chat message that spans the fold and grows at its bottom, draggingscrollTopdownward token by token (#1218). Re-measurements now only compensate items that are entirely above the fold (itemStart + itemSize <= scrollOffset); growth below the anchor point leaves the scroll position untouched. First measurements (estimate→actual) still compensate any above-fold item, and a customshouldAdjustScrollPositionOnItemSizeChangestill overrides the default.
3.17.5
20 July 2026Clamp the tracked scrollOffset at 0 when applying end-anchor measurement compensation and when re-anchoring in setOptions. Previously, with anchorTo: 'end' and content shorter than the viewport, items measuring smaller than their estimates drove the tracked offset negative with no scroll event to ever correct it — getDistanceFromEnd() reported a permanent phantom distance and iOS deferred measurement corrections stayed wedged forever.
Patch Changes
Clamp the tracked
scrollOffsetat 0 when applying end-anchor measurement compensation and when re-anchoring insetOptions. Previously, withanchorTo: 'end'and content shorter than the viewport, items measuring smaller than their estimates drove the tracked offset negative with no scroll event to ever correct it —getDistanceFromEnd()reported a permanent phantom distance and iOS deferred measurement corrections stayed wedged forever.Stop iOS-deferred scroll adjustments from replaying stale deltas after the position is already correct (#1233). On iOS WebKit the end-anchored virtualizer defers scroll compensation while the scroller is live and replays it once settled, but two cases replayed a delta whose premise no longer held:
Absolute scroll commands (
scrollToOffset/scrollToIndex/scrollToEnd) derive their target from current measurements, so a pending deferred delta is already stale — it now invalidates the deferral instead of letting it replay and shift the list off the just-established position. Relative commands (scrollBy) keep the deferral.At the end clamp with
anchorTo: 'end', a row above the viewport re-measuring smaller lets the browser clampscrollToponto the new bottom (already the correct position); the flush now drops the stale negative compensation instead of replaying it and lifting the view off the bottom. Positive deltas still replay, since content growth above does not clamp.
Quality and security signals
What npm and deps.dev say about the latest release. Signals, not a verdict.
Reading npm and deps.dev…
Registry facts last checked 15 October 2026. Releases and advisories last read 15 October 2026.
Know when your own dependencies move
Inlinr tracks your coding time automatically. Switch dependency tracking on for a project and it reads that repository's manifest to show what is behind and what has been deprecated — off by default, one project at a time.