Virtual Scroll Comparison

@pdanpdan/virtual-scroll vs. other main Vue 3 virtualization libraries. Contender versions, publish dates, star counts and bundle sizes were checked on 2026-09-22. This package's row is rebuilt on every docs build, so its version and sizes always match its current source.

Virtual scrolling looks like a solved problem until you need grid mode, window scrolling, or keyboard navigation and the library you picked only does one axis with fixed heights. This comparison covers Vue 3 packages and what each one ships today.

Eight framework libraries made the cut: this one, the veteran vue-virtual-scroller, the headless @tanstack/vue-virtual, the small and fast virtua, vueuc (a utility collection that happens to include a virtual list), vue-virtual-scroll-list, which is here mostly to show what an unmaintained package looks like, @ceriousdevtech/vue-cerious-scroll, the component + composable newcomer with measured sizes, and vlist, a framework-agnostic core with official adapters and a plugin for everything.

The Contenders

PackageLatestPublishedGitHub starsMin / gzipFeature scoreAPI style
@pdanpdan/virtual-scroll2.2.02026-09-221277.8 / 22.6 kB47 / 51 (92%)Component + composables + extensions, plus a lean /core entry
↳ /core component59.6 / 17.1 kB35 / 51 (69%)
↳ headless composable32.5 / 10.3 kB19 / 51 (37%)Engine only - the observers, extensions and keyboard/scrollbar composables are separate imports
↳ headless + observers, keyboard, scrollbar40.8 / 13.1 kB25 / 51 (49%)Engine + the measurement, keyboard and scrollbar composables
vue-virtual-scroller3.0.52026-08-1210,79729.0 / 10.4 kB15.5 / 51 (30%)Components (Scroller, RecycleScroller, DynamicScroller, TableScroller)
@tanstack/vue-virtual3.13.392026-09-147,119 (monorepo)24.3 / 7.3 kB27 / 51 (53%)Headless composable (useVirtualizer)
virtua0.52.72026-09-223,7489.5 / 4.6 kB25 / 51 (49%)Components (VList, VGrid) + handle
vueuc0.4.662026-08-1931022.9 / 8.6 kB9 / 51 (18%)Component (VVirtualList)
vue-virtual-scroll-list2.3.52023-05-294,50515.3 / 4.9 kB10 / 51 (20%)Component
vlist (+ vlist-vue)2.8.12026-09-1517112.7 / 37.0 kB36.5 / 51 (72%)Plugin core (createVList) + Vue adapter (useVList)
@ceriousdevtech/vue-cerious-scroll1.2.02026-09-2116100.6 / 26.7 kB22 / 51 (43%)Component + composable

Bundle sizes: each library's entry for rendering a list, tree-shaken, dependencies bundled and Vue external - installed from npm at the version listed above and bundled with esbuild --bundle --minify --format=esm --external:vue, gzipped at level 9 and reported in kB (1000 bytes). The gzip figure covers the JavaScript and the stylesheet concatenated, so a row is what a page using that entry downloads. Each number includes the stylesheet that entry needs: ours, vue-virtual-scroller and vlist (vlist/styles) ship theirs as a separate import, while vueuc and vue-cerious-scroll inject theirs from JavaScript. For the component libraries that is one component (RecycleScroller, VList, VVirtualList, VirtualList, CeriousScroll); for @tanstack/vue-virtual it is useVirtualizer including the @tanstack/virtual-core it depends on, and for vlist it is vlist-vue's useVList (2.6.0 over the 2.8.1 core), which through vlist/config pulls in most of the plugin set (a11y, search, sortable, transition and tree are not among them) - the smaller numbers in its docs assume importing plugins by hand: its bare core with the stylesheet is 12.0Β kB gzip, 14.1Β kB once the scrollbar plugin is added. virtua is measured from its virtua/vue entry, since the package root is the React build. Ours is listed entry by entry - the package root with all six built-in extensions, the lean /core component, the headless useVirtualScroll composable, and that composable with the observer, keyboard and scrollbar ones - and each row's score counts only the features that entry ships, and it is measured from this package's built entry files on every docs build. Every other row is the published version listed, in the snapshot from 2026-09-22. vue-cerious-scroll bundles its runtime dependency (@ceriousdevtech/cerious-scroll) the way a consumer would.

Feature Matrix

Feature@pdanpdan
/virtual-scroll
vue-virtual-
scroller
@tanstack
/vue-virtual
virtuavueucvue-virtual-
scroll-list
vlist@ceriousdevtech/
vue-cerious-scroll
Vertical listβœ…βœ…βœ…βœ…βœ…βœ…βœ…βœ…
Horizontal listβœ…βœ…βœ…βœ…βŒβœ…βœ…βŒ
Two-dimensional gridβœ…βŒπŸŸ πŸŸ βŒβŒπŸŸ βŒ
Window (page) scrollingβœ…βŒβœ…βœ…βŒβœ…βœ…βŒ
Native scroll container (browser wheel/touch physics)βœ…βœ…βœ…βœ…βœ…βœ…βœ…βŒ
Fixed item sizesβœ…βœ…βœ…βœ…βœ…βœ…βœ…βœ…
Measured dynamic item sizesβœ…βœ…βœ…βœ…βœ…πŸŸ βœ…βœ…
Measured rows without size estimates (no default-size first paint)βŒβŒβŒβŒβŒβŒβŒβœ…
RTLβœ…βŒπŸŸ βœ…βŒβŒβŒβŒ
Sticky elements / headersβœ…βŒβŒβŒβŒβŒβœ…βŒ
Keyboard navigationβœ…βŒβŒβŒβŒβŒβœ…βœ…
Scroll snappingβœ…βŒπŸŸ βŒβŒβŒπŸŸ βŒ
Custom / virtual scrollbarsβœ…βŒπŸŸ βŒβŒβŒβœ…βŒ
Infinite loadingβœ…πŸŸ βœ…βœ…πŸŸ πŸŸ βœ…πŸŸ 
Scroll restoration (prepend)βœ…βŒπŸŸ βœ…βŒβŒπŸŸ βŒ
Smooth programmatic scrollβœ…βœ…βœ…βœ…βœ…βŒβœ…βŒ
SSR supportβœ…βŒβœ…βœ…βŒβŒβŒβŒ
Beyond browser max element heightβœ…βŒβŒβŒβŒβŒβœ…βŒ
Table virtualizationβœ…βœ…πŸŸ πŸŸ βŒβŒβœ…βœ…
Masonry layout (one scroll container)βœ…βŒβŒβŒβŒβŒβœ…βœ…
Real table rows in flow + auto-size columnsβœ…πŸŸ βŒβŒβŒβŒβŒβœ…
Index-only lists (no per-row data objects)βœ…βŒβœ…βŒβŒβŒπŸŸ βœ…
Custom item identity keys (getItemKey / getKey)βŒβŒβœ…βœ…βŒβŒβŒβŒ
Automatic ARIA roles & item indicesβœ…βŒβŒβŒβŒβŒβœ…βŒ
Official siblings for other frameworks (React, Solid, …)βŒβŒβœ…βœ…βŒβŒβœ…βœ…
TypeScriptβœ…βœ…βœ…βœ…βœ…βŒβœ…βœ…
Feature score of 514715.5272591036.522

βœ… built-in Β β€’Β  🟠 partial, limited, or requires custom code Β β€’Β  ❌ not supported
Feature score weights each feature by how much of a library it is - 3 an engine of its own, 2 a built-in subsystem, 1 table stakes - with βœ… counting full weight, 🟠 half and ❌ none, out of 51.

Notes on the Orange Cells

🟠
@tanstack/vue-virtual

Headless by design - you own the markup, so everything not listed as βœ… is work you do yourself. Grid means composing two virtualizers, RTL is an isRtl flag that its own ecosystem describes as partial, and there is no scrollbar or snap logic to borrow. In exchange you get the smallest footprint of the serious options and total control.

🟠
virtua

The grid is named experimental_VGrid, and its own README still lists sticky and keyboard navigation as things it is β€œaiming” to support. What is shipped is fast and the window-scroll story is good. One codebase for five frameworks, but issues get filed against the shared core.

🟠
vue-virtual-scroll-list

Sized via an estimate cache (estimate-size), never measured, so scrollbar accuracy is approximate. The last release is from May 2023, the README still reads like Vue 2, and there are no TypeScript types. Its 4,500 stars are historical.

🟠
vue-virtual-scroller

Infinite loading means wiring your own scroll listener. It renders its own scroll container, so page scrolling needs a wrapper that reports to it. Solid components, and v3.0 is still actively released, but the feature set has stayed roughly the same since the Vue 2 days.

🟠
vueuc

VVirtualList powers Naive UI's large lists and it works, but vueuc is an internal utility collection - the author's README says: β€œI'm too lazy to write them since I'm the only one that uses the library.” Adopting it means depending on Naive UI's private parts.

🟠
@ceriousdevtech/vue-cerious-scroll

It measures rows and ships masonry and real table layouts, but infinite loading is manual: detect the near-end condition in your own handler, append, then call updateTotalElements and re-render. It is also the only entry that does not scroll natively: wheel/touch/keyboard handling runs on its own hidden-overflow host instead of the browser's scrollport. The ❌s are its own roadmap - horizontal virtualization, grids and sticky headers are listed there as future work.

🟠
vlist

The grid is a single-axis layout - columns and a gap over a vertically virtualized list, with no horizontal window, the same shape as virtua's grid. Snapping lives inside the carousel plugin, so a plain list has none. RTL is not implemented: the only RTL code in the package throws for horizontal lists in synthetic mode. Prepending data shifts what you are looking at - scroll snapshots and the rebuild utility preserve positions across navigation and list recreation, not across an insert above the viewport. The table renders div rows with resizable columns, not real table rows with content-sized columns. Rows have to exist as an array (the examples build Array.from({ length: n })), though the instance exposes setGetItemFn for index-driven rows. Its 37.0Β kB here is the adapter import: vlist/config pulls in most of the plugin set, so the smaller numbers in its docs assume importing plugins by hand.

Where Each One Wins

  • @pdanpdan/virtual-scroll - the only option that covers grid, window scrolling, RTL, sticky and keyboard navigation on its own, the only one that virtualizes past the browser's max element height, and the only Vue library that ships masonry and real table-flow layouts as dedicated components (heights from a model or measured from the DOM, single scroll container) on top of a native scroll container. Index-only datasets with flat memory, automatic ARIA roles, virtual scrollbars and a headless composable + extensions surface round out the picture. It is also one of the larger bundles here - 22.6Β kB gzip for the package root and 17.1Β kB for the lean /core component, behind only vlist and vue-cerious-scroll. The headless engine is 10.3Β kB at 19 of 51 points, 13.1Β kB at 25 with the observer, keyboard and scrollbar composables wired in, and 15.9Β kB at 36 with every exported extension too - 1.8, 1.9 and 2.3 points per kB gzip. Marginal bytes buy the most from the extensions (3.9 points per kB) and the least from keyboard navigation (1.3), which is why the component entries score better per kB than the headless ones. Per kB the small single-purpose libraries still lead (virtua 5.4, @tanstack/vue-virtual 3.7) - a ratio nothing with these many features can match. At 12 stars it has no community to fall back on.
  • @ceriousdevtech/vue-cerious-scroll - the only other Vue entry that ships real table rows and masonry layouts (canonical or DOM-measured), with built-in keyboard navigation. It is vertical-only - no grid, no horizontal mode, no window scrolling, RTL, sticky, snapping, SSR or beyond-browser-max support - and it scrolls on its own hidden-overflow host instead of the browser's scrollport. The heaviest per-view code here after vlist (26.7Β kB gzip, its runtime dependency bundled) and at 16 stars it has no community either.
  • vlist - the other batteries-included entry, and the closest in scope: a framework-agnostic core with official Vue, React, Svelte and Solid adapters, plus plugins for grid, masonry, table, tree, groups, selection, search, sortable, snapshots, transition, carousel and async data. Its core is the smallest serious one when you import plugins by hand (12.0Β kB with the stylesheet, 14.1Β kB once the scrollbar plugin is added), the default scrollbar is already an overlay, document scrolling is a plugin, and 1M+ items work through its bounded and synthetic scroll modes (the older scale() plugin is now a deprecation stub). What it does not have is a second axis, RTL, snapping outside the carousel, real table rows, or prepend anchoring - and the adapter path bundles every plugin. As young as this project: first release this year, 17 stars.
  • vue-virtual-scroller - the safe, boring choice. Ten years of production use, active releases, every layout problem already answered on GitHub. You give up window scrolling, RTL and anything 2D.
  • @tanstack/vue-virtual - when the list is a plain list and you want the smallest dependency with the most control. You will write the rest yourself, which is fine for one axis of fixed or measured items.
  • virtua - the current speed champion with the best cross-framework story. Window scrolling and measured sizes work; everything beyond a plain list is still maturing.
  • vueuc - only makes sense if you are already on Naive UI.
  • vue-virtual-scroll-list - don't start new projects with it.

Bottom Line

If your list is vertical and your items have known sizes, every current library works - pick by bundle size or familiarity. The differences only show up when you need the second axis, the page as the scroll container, sticky headers, keyboard support, or lists that outgrow the browser's height limit. That is exactly where @pdanpdan/virtual-scroll is positioned, and the trade-off is a bigger bundle and a young project. The demos on this site - grid, blog, spreadsheet, tree, table flow, masonry - are each a feature the mainstream competitors cannot do without custom code. vue-cerious-scroll also ships masonry and real table rows, but stops there (vertical only, no native scrollport, no grid, window, RTL or beyond-max support); vlist matches the layout plugin list but virtualizes one axis at a time and has no RTL.

Sources: npm registry, GitHub API and project docs, checked on 2026-09-22. vue-virtual-scroller Β β€’Β  @tanstack/vue-virtual Β β€’Β  virtua Β β€’Β  vueuc Β β€’Β  vue-virtual-scroll-list Β β€’Β  @ceriousdevtech/vue-cerious-scroll Β β€’Β  vlist (Vue adapter)