---
id: "packages/active-virtual/examples"
title: "Examples"
description: "Every working example of active-virtual on one page. Each one shows the file that renders it."
url: "/packages/active-virtual/examples"
locale: "en"
revision: "18da15cf979658ed"
---

# Examples

Both examples are **Client** examples: the list and its measurements live in your browser.

**Example: Ten thousand rows of varying height**

Client: Runs entirely in your browser. Source: `examples/virtual/long-list.tsx`.

```tsx
// apps/docs/examples/virtual/long-list.tsx
'use client';

import {useState} from 'react';

import {Button} from '@workspace/active-ui/components/primitives/button';
import {Input} from '@workspace/active-ui/components/primitives/input';
import {Label} from '@workspace/active-ui/components/primitives/label';
import {useVirtualAxis} from '@workspace/active-virtual/react';

const COUNT = 10_000;

/** Every seventh row wraps to a second line: the axis measures real heights. */
function rowText(position: number): string {
  const base = `Row ${(position + 1).toLocaleString('en')}`;
  return position % 7 === 3
    ? `${base}: a longer note that wraps to a second line in a narrow column, so this row is taller than its estimate.`
    : base;
}

/** 10,000 rows of varying height; only the rows in view (plus overscan) are in the DOM. */
export default function LongList() {
  const [container, setContainer] = useState<HTMLDivElement | null>(null);
  const [target, setTarget] = useState('5000');
  const {projection, controller, itemRef} = useVirtualAxis({
    count: COUNT,
    estimate: 32,
    container,
  });
  return (
    <div className="flex flex-col gap-3">
      <form
        className="flex flex-wrap items-end gap-2"
        onSubmit={(event) => {
          event.preventDefault();
          const row = Number(target);
          if (Number.isInteger(row) && row >= 1 && row <= COUNT)
            controller.scrollToPosition(row - 1, {align: 'center'});
        }}
      >
        <div className="flex flex-col gap-1.5">
          <Label htmlFor="virtual-target">Row</Label>
          <Input
            id="virtual-target"
            className="w-28"
            inputMode="numeric"
            value={target}
            onChange={(event) => setTarget(event.target.value)}
          />
        </div>
        <Button type="submit" variant="outline">
          Go to row
        </Button>
      </form>
      <div
        ref={setContainer}
        className="bg-background h-72 overflow-auto rounded-md border"
        tabIndex={0}
        aria-label="Ten thousand rows"
        data-virtual-container
      >
        <div style={{height: projection.extent, position: 'relative'}}>
          {projection.items.map((item) => (
            <div
              key={item.identity}
              ref={itemRef(item.identity)}
              className="border-b px-3 py-1.5 text-sm"
              style={{
                position: 'absolute',
                top: 0,
                left: 0,
                width: '100%',
                transform: `translateY(${item.offset}px)`,
              }}
              data-position={item.position}
            >
              {rowText(item.position)}
            </div>
          ))}
        </div>
      </div>
      <p className="text-muted-foreground text-sm" aria-live="polite">
        {projection.items.length} of {COUNT.toLocaleString('en')} rows in the
        DOM
      </p>
    </div>
  );
}
```

**Example: Older items above the viewport, without a jump**

Client: Runs entirely in your browser. Source: `examples/virtual/keyed-prepend.tsx`.

```tsx
// apps/docs/examples/virtual/keyed-prepend.tsx
'use client';

import {useState} from 'react';

import {Button} from '@workspace/active-ui/components/primitives/button';
import {useVirtualAxis} from '@workspace/active-virtual/react';

interface Message {
  key: string;
  text: string;
}

function messages(from: number, count: number): Message[] {
  return Array.from({length: count}, (_, index) => {
    const n = from + index;
    return {key: `m${n}`, text: `Message ${n}`};
  });
}

/**
 * A keyed list: loading older messages above the viewport does not move what
 * is on screen, because the axis anchors on a key and compensates the scroll.
 */
export default function KeyedPrepend() {
  const [container, setContainer] = useState<HTMLDivElement | null>(null);
  const [items, setItems] = useState(() => messages(100, 50));
  const {projection, itemRef} = useVirtualAxis({
    keys: items.map((item) => item.key),
    estimate: 36,
    container,
    // Keep the item at the top in place even when the list is scrolled to
    // its very start, instead of following that edge.
    startEdge: 'preserve',
  });
  const byKey = new Map(items.map((item) => [item.key, item]));
  const first = Number(items[0]!.key.slice(1));
  return (
    <div className="flex flex-col gap-3">
      <div className="flex flex-wrap items-center gap-2">
        <Button
          variant="outline"
          disabled={first === 0}
          onClick={() =>
            setItems((list) => [
              ...messages(Math.max(0, first - 20), Math.min(20, first)),
              ...list,
            ])
          }
        >
          Load 20 older messages
        </Button>
        <span className="text-muted-foreground text-sm" aria-live="polite">
          {items.length} messages, from {items[0]!.text}
        </span>
      </div>
      <div
        ref={setContainer}
        className="bg-background h-60 overflow-auto rounded-md border"
        tabIndex={0}
        aria-label="Messages"
        data-virtual-container
      >
        <div style={{height: projection.extent, position: 'relative'}}>
          {projection.items.map((item) => (
            <div
              key={item.identity}
              ref={itemRef(item.identity)}
              className="border-b px-3 py-2 text-sm"
              style={{
                position: 'absolute',
                top: 0,
                left: 0,
                width: '100%',
                transform: `translateY(${item.offset}px)`,
              }}
            >
              {byKey.get(String(item.identity))?.text}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}
```
