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
| Package | Latest | Published | GitHub stars | Min / gzip | Feature score | API style |
|---|---|---|---|---|---|---|
| @pdanpdan/virtual-scroll | 2.2.0 | 2026-09-22 | 12 | 77.8 / 22.6 kB | 47 / 51 (92%) | Component + composables + extensions, plus a lean /core entry |
β³ /core component | 59.6 / 17.1 kB | 35 / 51 (69%) | ||||
| β³ headless composable | 32.5 / 10.3 kB | 19 / 51 (37%) | Engine only - the observers, extensions and keyboard/scrollbar composables are separate imports | |||
| β³ headless + observers, keyboard, scrollbar | 40.8 / 13.1 kB | 25 / 51 (49%) | Engine + the measurement, keyboard and scrollbar composables | |||
| vue-virtual-scroller | 3.0.5 | 2026-08-12 | 10,797 | 29.0 / 10.4 kB | 15.5 / 51 (30%) | Components (Scroller, RecycleScroller, DynamicScroller, TableScroller) |
| @tanstack/vue-virtual | 3.13.39 | 2026-09-14 | 7,119 (monorepo) | 24.3 / 7.3 kB | 27 / 51 (53%) | Headless composable (useVirtualizer) |
| virtua | 0.52.7 | 2026-09-22 | 3,748 | 9.5 / 4.6 kB | 25 / 51 (49%) | Components (VList, VGrid) + handle |
| vueuc | 0.4.66 | 2026-08-19 | 310 | 22.9 / 8.6 kB | 9 / 51 (18%) | Component (VVirtualList) |
| vue-virtual-scroll-list | 2.3.5 | 2023-05-29 | 4,505 | 15.3 / 4.9 kB | 10 / 51 (20%) | Component |
| vlist (+ vlist-vue) | 2.8.1 | 2026-09-15 | 17 | 112.7 / 37.0 kB | 36.5 / 51 (72%) | Plugin core (createVList) + Vue adapter (useVList) |
| @ceriousdevtech/vue-cerious-scroll | 1.2.0 | 2026-09-21 | 16 | 100.6 / 26.7 kB | 22 / 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 | virtua | vueuc | vue-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 51 | 47 | 15.5 | 27 | 25 | 9 | 10 | 36.5 | 22 |
β
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
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.
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.
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.
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.
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.
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.
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
/corecomponent, behind onlyvlistandvue-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 (virtua5.4,@tanstack/vue-virtual3.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)