Skip to content

Layout

Virtual list

Renders only the rows in view, for very long lists.

import { VirtualList } from '@jsonresume/ui'Source(opens in a new tab)

Examples

5,000 rows
    Show code
    virtual-list/log.tsx
    'use client';
    
    import { Text, VirtualList } from '@jsonresume/ui';
    import * as styles from './log.css';
    
    const events = Array.from({ length: 5000 }, (_, i) => ({
      id: i,
      text: `Matched job #${5000 - i} against your resume`,
      score: 40 + ((i * 37) % 60),
    }));
    
    // 5,000 rows; only the visible ones are in the DOM.
    export default function VirtualListLog() {
      return (
        <div className={styles.frame}>
          <VirtualList
            label="Match log"
            height="18rem"
            estimateSize={44}
            items={events}
            getKey={(e) => e.id}
            renderItem={(e) => (
              <div className={styles.row}>
                <Text size="sm">{e.text}</Text>
                <Text size="xs" tone="muted">
                  {e.score}% match
                </Text>
              </div>
            )}
          />
        </div>
      );
    }
    virtual-list/log.css.ts
    import { style } from '@vanilla-extract/css';
    import { vars } from '@jsonresume/ui/tokens';
    
    export const frame = style({
      border: `1px solid ${vars.color.border.default}`,
      borderRadius: vars.radius.sm,
      backgroundColor: vars.color.surface.raised,
    });
    
    export const row = style({
      display: 'flex',
      justifyContent: 'space-between',
      alignItems: 'center',
      gap: vars.space[3],
      padding: `${vars.space[3]} ${vars.space[4]}`,
      borderBottom: `1px solid ${vars.color.border.subtle}`,
    });

    Accessibility

    • Rows keep aria-setsize and aria-posinset so their position in the full list is known.
    • The scroll region is focusable and labelled.

    Props

    <VirtualList>

    Also accepts the attributes of the element VirtualList renders.
    PropTypeDefaultDescription
    height requiredstring–Visible height (any CSS length).
    items requiredT[]–
    label requiredstring–
    renderItem required(item: T, index: number) => ReactNode–Renders one item; keep row heights close to estimateSize.
    estimateSizenumber56Estimated row height in px; real heights are measured.
    getKey((item: T, index: number) => string | number)–