---
title: "Virtual Data Table"
description: "The portal's virtualized history table: rows scroll inside the table's own frame, with infinite scrollback, live-tail follow, and jump-to-latest."
component: true
---

```tsx
"use client"

import { useMemo, useState } from "react"
import type { ColumnDef } from "@tanstack/react-table"
import { VirtualDataTable } from "@/components/ui/virtual-data-table"
import { StatusCodeBadge } from "@/components/ui/status-code-badge"

type DemoRow = {
  id: string
  time: string
  route: string
  status: number
  durationMs: number
}

const ROUTES = ["/v1/messages", "/v1/endpoints", "/v1/logs", "/v1/replays", "/v1/events"]
const BASE_TIME = Date.parse("2026-07-30T12:00:00.000Z")

/** 5,000 deterministic rows — only the visible ones render. */
const buildRows = (): DemoRow[] =>
  Array.from({ length: 5000 }, (_, index) => ({
    id: `req_${index}`,
    time: new Date(BASE_TIME - index * 11_000).toLocaleTimeString(),
    route: ROUTES[index % ROUTES.length]!,
    status: index % 23 === 7 ? 429 : index % 17 === 3 ? 500 : 200,
    durationMs: 12 + ((index * 37) % 240),
  }))

export function VirtualDataTableDemo() {
  const [selected, setSelected] = useState<string | null>(null)
  const rows = useMemo(() => buildRows(), [])

  const columns = useMemo<ColumnDef<DemoRow, unknown>[]>(
    () => [
      {
        id: "time",
        meta: { label: "Time" },
        size: 120,
        cell: ({ row }) => <span className="font-mono text-muted-foreground tabular-nums">{row.original.time}</span>,
      },
      {
        id: "route",
        meta: { label: "Route" },
        size: 220,
        cell: ({ row }) => <span className="truncate font-medium">{row.original.route}</span>,
      },
      {
        id: "status",
        meta: { label: "Status" },
        size: 90,
        cell: ({ row }) => <StatusCodeBadge statusCode={row.original.status} />,
      },
      {
        id: "duration",
        meta: { label: "Duration" },
        size: 100,
        cell: ({ row }) => <span className="font-mono text-muted-foreground tabular-nums">{row.original.durationMs}ms</span>,
      },
    ],
    []
  )

  return (
    <VirtualDataTable
      className="h-[360px] w-full"
      data={rows}
      columns={columns}
      getRowId={(row) => row.id}
      onRowClick={(row) => setSelected((current) => (current === row.id ? null : row.id))}
      selectedRowId={selected}
      fillColumnId="route"
      rounded
      borderX
    />
  )
}

```

Rows render through `@tanstack/react-virtual` inside the table's own scroll
container: the page never scrolls on the table's behalf, and five thousand
rows cost the same as fifty. The column header stays fixed above the scroll
area. Everything stateful is driven by callbacks, so any data source works.

Set `inverted` for log-style feeds: newest rows sit at the visual bottom,
scrollback loads upward, and the bottom strip becomes the live-tail control
(with Ctrl+Space) when `onToggleStream` is wired.

## Installation

<ItemInstall name="virtual-data-table" target="components/ui/virtual-data-table.tsx" />

## Usage

<ComponentSource name="virtual-data-table-demo" title="components/virtual-data-table-demo.tsx" />

## Props

| Prop | Type | Description |
| ---- | ---- | ----------- |
| `data` / `columns` / `getRowId` | — | TanStack table inputs; column `meta.label` names the header. |
| `className` | `string` | Size the frame here: the table scrolls internally (e.g. `h-[360px]`, or `min-h-0 flex-1`). |
| `inverted` | `boolean` | Log-style: newest at the visual bottom, scrollback above. |
| `onScrollTop` / `onScrollBottom` | `() => void` | Infinite scroll in visual directions; pair with `isLoadingTop` / `isLoadingBottom`. |
| `onRowClick` / `selectedRowId` | — | Row activation and highlight. |
| `streaming` / `streamStatus` / `onToggleStream` | — | Live tail: auto-stick at the bottom, a "Streaming · N new" pill when scrolled away, and the tail strip. |
| `streamDroppedCount` / `onClearStreamDropped` | — | Surface and clear rows the stream dropped. |
| `hasNewer` / `onLoadNewer` / `checkForNewer` | — | Jump-to-latest and background polling when not streaming. |
| `initialScrollToId` | `string` | Scroll to an anchor row on mount. |
| `columnVisibility` / `columnOrder` / `columnSizing` | — | Controlled column state (TanStack shapes). |
| `estimateSize` | `number` | Row height estimate for the virtualizer. Default 36. |
| `emptyMessage` | `ReactNode` | Shown when there are no rows. |
