/ Tutorials

Quick Start #

Get started with vlist in under 5 minutes.

Installation #

npm install @floor/vlist

Examples #

1. Simple List #

import { vlist } from '@floor/vlist';
import '@floor/vlist/styles';

const list = vlist({
  container: '#app',
  items: [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    { id: 3, name: 'Charlie' },
  ],
  item: {
    height: 48,
    template: (item) => `<div>${item.name}</div>`,
  },
}).build();

Bundle: 7.7 KB gzipped


2. With Selection #

import { vlist, withSelection } from '@floor/vlist';

const list = vlist({
  container: '#app',
  items: users,
  item: {
    height: 48,
    template: (user, index, { selected }) => {
      const cls = selected ? 'selected' : '';
      return `<div class="${cls}">${user.name}</div>`;
    },
  },
})
  .use(withSelection({ mode: 'single' }))
  .build();

// Selection API
list.select(5);
list.getSelected();       // [5]
list.getSelectedItems();  // [{ id: 5, ... }]

Bundle: 10.0 KB gzipped


import { vlist, withGrid, withScrollbar } from '@floor/vlist';

const gallery = vlist({
  container: '#gallery',
  items: photos,
  item: {
    height: 200,
    template: (photo) => `
      <div class="card">
        <img src="${photo.url}" />
        <span>${photo.title}</span>
      </div>
    `,
  },
})
  .use(withGrid({ columns: 4, gap: 16 }))
  .use(withScrollbar({ autoHide: true }))
  .build();

Bundle: 11.7 KB gzipped


4. Contact List (A-Z Sections) #

import { vlist, withGroups } from '@floor/vlist';

const contacts = vlist({
  container: '#contacts',
  items: sortedContacts,  // Pre-sorted by lastName!
  item: {
    height: 56,
    template: (contact) => `<div>${contact.name}</div>`,
  },
})
  .use(withGroups({
    getGroupForIndex: (i) => contacts[i].lastName[0].toUpperCase(),
    headerHeight: 36,
    headerTemplate: (letter) => `<div class="header">${letter}</div>`,
    sticky: true,
  }))
  .build();

Bundle: 12.3 KB gzipped


5. Chat UI (Reverse Mode) #

import { vlist, withGroups } from '@floor/vlist';

const chat = vlist({
  container: '#messages',
  reverse: true,   // Start at bottom
  items: messages, // Oldest first
  item: {
    height: (i) => messages[i].height || 60,
    template: (msg) => `<div class="msg">${msg.text}</div>`,
  },
})
  .use(withGroups({
    getGroupForIndex: (i) => formatDate(messages[i].timestamp),
    headerHeight: 32,
    headerTemplate: (date) => `<div class="date">${date}</div>`,
    sticky: false,  // Inline headers (iMessage style)
  }))
  .build();

// New messages auto-scroll to bottom
chat.appendItems([newMessage]);

// Load history preserves scroll
chat.prependItems(olderMessages);

Bundle: 11.9 KB gzipped


6. Infinite Scroll Feed #

import { vlist, withPage, withAsync } from '@floor/vlist';

const feed = vlist({
  container: '#feed',
  item: {
    height: 300,
    template: (post) => {
      if (!post) return `<div class="skeleton">Loading...</div>`;
      return `<article>${post.content}</article>`;
    },
  },
})
  .use(withPage())   // Document-level scrolling
  .use(withAsync({
    adapter: {
      read: async ({ offset, limit }) => {
        const res = await fetch(`/api/posts?offset=${offset}&limit=${limit}`);
        return res.json();
      },
    },
    loading: { cancelThreshold: 15 },
  }))
  .build();

Bundle: 13.5 KB gzipped


7. Large Dataset (1M+ Items) #

import { vlist, withScale, withScrollbar } from '@floor/vlist';

const bigList = vlist({
  container: '#list',
  items: generateItems(5_000_000),
  item: {
    height: 48,
    template: (item) => `<div>#${item.id}: ${item.name}</div>`,
  },
})
  .use(withScale())   // Auto-activates scaling for large datasets
  .use(withScrollbar({ autoHide: true }))
  .build();

Bundle: 9.9 KB gzipped


Common Patterns #

Variable Heights #

const list = vlist({
  container: '#list',
  items: messages,
  item: {
    height: (index) => messages[index].measuredHeight || 60,
    template: (msg) => `<div>${msg.text}</div>`,
  },
}).build();

Horizontal Scrolling #

const carousel = vlist({
  container: '#carousel',
  orientation: 'horizontal',
  items: cards,
  item: {
    width: 300,
    height: 400,
    template: (card) => `<div>${card.content}</div>`,
  },
}).build();

Scroll to Item #

list.scrollToIndex(50, 'center');
list.scrollToItem('user-123', { align: 'start', behavior: 'smooth', duration: 500 });

Events #

list.on('scroll', ({ scrollTop, direction }) => {
  console.log('Scrolled to:', scrollTop);
});

list.on('item:click', ({ item, index }) => {
  console.log('Clicked:', item);
});

Data Updates #

list.setItems(newItems);
list.appendItems([item1, item2]);
list.prependItems([item0]);
list.updateItem(5, { name: 'Updated' });
list.removeItem(5);

Available Features #

Feature Cost Description
withGrid() +4.0 KB 2D grid layout
withGroups() +4.6 KB Grouped lists with headers
withAsync() +5.3 KB Async data loading
withSelection() +2.3 KB Item selection
withScale() +2.2 KB Handle 1M+ items
withScrollbar() +1.0 KB Custom scrollbar
withPage() +0.9 KB Page-level scrolling

Next Steps #