/ Docs

Examples #

PublishedUpdated Sep 23, 2026

The examples are interactive pages for the vlist 3 behavior. Each page is a small app: a list, a side panel, and the source that built it. The live pages are vlist.io/examples.

Library scroll modes are described in Scroll modes. This page is how the example shell applies them.

Catalog #

Essentials #

Example What it shows
Basic List Item count, sizing, overscan, scroll-to, and data operations. Vanilla, React, Vue, Svelte, and Solid.
Photo Album Grid and masonry, with selection, groups, and snapshots.
Messaging Reverse chat, date headers, and incoming messages.
Contact List A–Z groups, sticky or inline headers, and selection.
Data Table Resizable columns, sortable headers, and row selection.
Social Feed Variable-height posts measured with autosize().
Carousel Infinite snap carousel. Follows the scroll switch.
Plugin Wizard Carousel used as a plugin explorer.
Window Scroll The document scrolls, not an inner list. Scroll mode is locked.
Track List Lazy music library. List, grid, and table. Chooses the entry itself.
Accessibility Listbox roles and aria-activedescendant, updated live.

Specific #

Example What it shows
Velocity Loading Async loading that skips fetches during a fast scroll.
Code Explorer vlist's own source: tree, symbols, and search.
Large Dataset 100K to 20M items. List, grid, and table.
Variable Sizes Per-item heights and auto-measured DOM sizes.
Scroll Restore Save and restore a scroll position across navigations.
Scrollbar Native, custom, and none, with its own controls.
Sortable Drag-and-drop reorder.
Tree View Expand, collapse, keyboard navigation, and type-ahead.

Other #

Example What it shows
File Browser Finder-like table and grid.
Phone pass Device checks for drag, momentum, and carousel wrap. Scroll mode is locked.

Scroll switch #

Every example side panel starts with a Scroll control: Auto, Native or Synthetic, the three values of `scroll.mode`. It is injected by the example shell, not copied into each example.

The choice is remembered for the browser session in the cookie vlist-scroll-mode. ?mode=auto, ?mode=native or ?mode=synthetic wins over the cookie. With neither, the page uses Auto, the library's default.

Switching modes does not reload the page. The shell calls rebuild() (Rebuild): the new list is drawn hidden at the previous size, then swapped in, and the scroll position comes back with it. While that hidden list is being drawn it is pinned to the old list's width and height, so a layout plugin that skips a zero-size container still paints the first frame.

An example that imports createVList from "vlist" gets this automatically. The example bundler wraps that one export and passes the switch as scroll.mode. An example that sets scroll.mode in its own config keeps it: the switch does not override it.

Locked pages #

A page with data-scroll-mode="locked" does not show the switch and stays native. The list on that page is not a normal scroller:

Pages that choose the mode themselves #

Phone pass sets scroll.mode: "synthetic" on its momentum and carousel tests, which drive touch under synthetic input.

Scrollbar pins "native" for its Native choice (the browser bar) and "synthetic" for None (no bar); Custom follows the switch.

Large Dataset follows the switch. Under Auto, its info bar shows who owns input right now (AUTO · SYNTHETIC above about 330,000 rows of 48px), from the list's scroll:mode event. With Native, sizes past the browser's limit are flagged: the list cannot reach its last rows.

Scrollbar #

Native mode shows the browser scrollbar. Synthetic input has none, because the viewport does not scroll: a synthetic list draws its own, and Auto is native until a list passes the browser's size limit.

  • In Native, the shell removes a custom scrollbar() plugin so the browser bar is visible.
  • Otherwise the shell leaves the example's plugins alone: a list that goes synthetic draws its scrollbar by itself, or uses the example's scrollbar() if it passed one.
  • Scrollbar is marked data-scrollbar-owned. The shell leaves its plugins alone, and its own Native / Custom / None control stays in charge.

Recreating a list inside an example #

Some controls rebuild the list with different plugins, for example the contact list header (sticky, inline, off) and the gutter. Those examples do it themselves: they take getScrollSnapshot(), destroy the list, and pass snapshots({ restore }) into the next createVList. The shell keeps that plugin, so the list returns to the same position.

A Native / Synthetic switch is different. rebuild() supplies its own snapshots plugin for that swap, and the shell uses that one instead of the example's, so the position is restored once.

Building #

From vlist.io:

bun run dev                 # build examples and benchmarks, then watch the server
bun run build:examples      # dist/examples only
bun examples/build.ts --force

Pages serve the bundles in dist/examples. The build cache is the example directory plus the vlist dist hash. A change to examples/build.ts is not part of that hash, so rebuild with --force after editing the wrapper. A library change needs bun run build in vlist first, then a forced example build.

?variant= selects a framework directory when the example has one (vanilla, react, vue, svelte, solidjs). The scroll wrapper applies to createVList imported from "vlist". Pages that use a framework entry (vlist/react, vlist/vue, vlist/svelte, vlist/solid) are not wrapped: the entry builds its list with the core createVList directly.