vlist #
High-performance virtual list engine for the web. Framework-agnostic core with first-class bindings for React, Vue, Solid, and Svelte.
Why vlist? #
Virtual list libraries have been around for years, but almost all of them are locked to a single framework. react-window, react-virtuoso, TanStack Virtual, Legend List — they all assume you're inside React.
vlist takes a different approach: vanilla first, framework optional.
| Library | Framework | Dependencies | Gzipped | Tree-shakeable |
|---|---|---|---|---|
| react-window | React | 1 | ~6 KB | No |
| react-virtuoso | React | 1 | ~16 KB | No |
| TanStack Virtual | Multi (React-dominant) | 1+ | ~10 KB | Partial |
| Legend List v3 | React / RN | 1+ | ~21 KB | No |
| vlist | None | 0 | 8.7 KB | Yes |
Zero runtime dependencies means no supply chain risk, no version conflicts, no transitive node_modules bloat. The entire library is built from scratch in TypeScript.
Constant Memory #
Most virtual list libraries create internal data structures that grow linearly with your dataset. vlist doesn't.
| Dataset size | vlist memory |
|---|---|
| 10,000 items | ~0.2 MB |
| 100,000 items | ~0.2 MB |
| 1,000,000 items | ~0.4 MB |
With 100,000 items in your dataset, roughly 26 DOM elements exist at any given time. The scroll handler runs at 120 FPS with zero allocations on the hot path.
Pay Only for What You Use #
vlist uses a builder pattern with opt-in features. The base is 8.7 KB gzipped. Each feature adds only what it needs:
| Configuration | Gzipped | Features |
|---|---|---|
| Base only | 10.3 KB | None |
| + Selection | 12.1 KB | withSelection() |
| + Grid | 14.3 KB | withGrid() + withScrollbar() |
| + Groups | 14.6 KB | withGroups() |
| + Async | 14.6 KB | withAsync() + withPage() |
| + Scale | 12.9 KB | withScale() + withScrollbar() |
| All features | ~33 KB | Everything |
Compare this to monolithic libraries where you ship every feature whether you use it or not.
Features #
Every feature is self-contained and tree-shakeable:
| Feature | How |
|---|---|
| Lists (vertical & horizontal) | Built-in |
| Grids | withGrid() |
| Masonry | withMasonry() |
| Data tables | withTable() |
| Drag-and-drop reordering | withSortable() |
| Grouped sections with sticky headers | withGroups() |
| Async/infinite loading | withAsync() |
| Multi-selection | withSelection() |
| 1M+ items with scroll compression | withScale() |
| Auto-measure items | withAutoSize() |
| Page-level scrolling | withPage() |
| Scroll save/restore | withSnapshots() |
| Custom scrollbar | withScrollbar() |
| Reverse mode (chat UIs) | reverse: true |
| Keyboard navigation & ARIA | Built-in |
Quick Start #
npm install vlist
import { vlist } from 'vlist';
import 'vlist/styles';
const list = vlist({
container: '#list',
items: data,
item: {
height: 48,
template: (item) => `<div class="row">${item.name}</div>`,
},
}).build();
That's a working virtual list. Thousands of items, only ~20 DOM nodes.
Framework Adapters #
vlist ships adapters for React, Vue, Svelte, and SolidJS. They're thin wrappers — a few lines of glue code that translate framework idioms (refs, hooks, reactivity) into vlist's vanilla API. The core never imports React, never calls useState, never touches a virtual DOM.
If your framework doesn't have an adapter yet, you can integrate vlist in an afternoon. It's just DOM.