# Data table: Virtual rows

> Render long lists in the slotsmith data table with the virtual prop, which draws only the rows in view.

For long lists, pass `virtual` to the data table with the virtualizer hook as `use`: it then renders only the rows in view, inside a scroll area with a sticky header. The hook is `useVirtualizer` from `@tanstack/react-virtual`.

```tsx
import { useVirtualizer } from "@tanstack/react-virtual";
import { DataTable, type DataTableColumnDef } from "slotsmith/data-table";

type LogLine = { id: number; time: string; level: string; service: string; message: string };

const LEVELS = ["info", "info", "info", "warn", "error"];
const SERVICES = ["api", "auth", "billing", "search", "worker"];
const MESSAGES = ["Request served", "Cache refreshed", "Token renewed", "Job queued", "Retrying upstream call"];

/** 10,000 log lines, the same on every render: line i is computed from i alone. */
const lines: LogLine[] = Array.from({ length: 10_000 }, (_, i) => {
  const seconds = i * 7;
  const time = [Math.floor(seconds / 3600) % 24, Math.floor(seconds / 60) % 60, seconds % 60]
    .map((part) => String(part).padStart(2, "0"))
    .join(":");
  return {
    id: i + 1,
    time,
    level: LEVELS[(i * 7) % LEVELS.length]!,
    service: SERVICES[(i * 3) % SERVICES.length]!,
    message: `${MESSAGES[(i * 11) % MESSAGES.length]} #${i + 1}`,
  };
});

const columns: DataTableColumnDef<LogLine>[] = [
  { accessorKey: "id", header: "#", meta: { align: "end" } },
  { accessorKey: "time", header: "Time" },
  { accessorKey: "level", header: "Level" },
  { accessorKey: "service", header: "Service" },
  { accessorKey: "message", header: "Message", enableSorting: false },
];

export default function VirtualRows() {
  return (
    <DataTable
      data={lines}
      columns={columns}
      getRowId={(line) => String(line.id)}
      // Only the rows in view are rendered, inside a scroll area this tall.
      // Rows are not measured: estimateSize is their height, so keep them on one line.
      virtual={{ use: useVirtualizer, estimateSize: 40, maxHeight: 360 }}
      slotProps={{ cell: () => ({ style: { whiteSpace: "nowrap" } }) }}
    />
  );
}
```

The table holds 10,000 log lines, built from their index so every render gets the same ones. Scroll it, or sort by a column: only the rows on screen exist in the page.

## Install

`@tanstack/react-virtual` is an optional peer: you pass its hook in, so slotsmith itself never imports it, and a project that never windows a list does not need it.

```sh
pnpm add @tanstack/react-virtual
```

## The virtual option

`virtual` takes the hook as `use`, and three numbers:

- `maxHeight`: the height of the scroll area, in pixels or as a CSS length. Defaults to 480.
- `estimateSize`: the height of one row, in pixels. Defaults to 40. Rows are not measured, so set it to the height your rows have, and keep them that height: the demo stops its cells from wrapping with `slotProps.cell`, so a narrow screen scrolls the table sideways instead of growing the rows.
- `overscan`: how many rows to render beyond the visible ones, so a fast scroll shows no blank space. Defaults to 10.

Pagination is off by default; [`enablePagination`](https://slotsmith.dev/components/data-table/api/#props) turns it back on. The table is compact (`size="sm"`) unless you pass `size="default"`.

Everything else works as in `DataTable`: [sorting](https://slotsmith.dev/components/data-table/guides/sorting-and-selection/), selection, [tree rows](https://slotsmith.dev/components/data-table/guides/tree-rows/), the loading, empty and error states, labels and every slot. Rows above and below the window are stood in for by empty spacer rows, so the native table layout, and every `Row` and `Cell` slot, stays as it is.

[Row reorder](https://slotsmith.dev/components/data-table/guides/row-reorder/) and [spreadsheet mode](https://slotsmith.dev/components/data-table/guides/spreadsheet/) work too. A dragged row stays rendered while it is scrolled out of the window, and the view scrolls under it near the edges; the keyboard scrolls to the row it moves to.

`VirtualDataTable` from `slotsmith/virtual` is the same table with `useVirtualizer` already passed in, for code written before `virtual` took the hook.

## Your own layout

With `virtual`, `DataTable` is `DataTable.Provider` with a virtual body. To place the parts yourself, render `DataTable.VirtualBody` as the `body` of `DataTable.Table`, with the hook as `use`. Keep the scroll area in state with the table's `scrollRef` (a callback ref such as a state setter), and pass that element to the body as `scrollElement`.
