Skip to content
Elements

Data table

A tool's rows and columns with formatting, sorting, and a card layout in narrow columns.

GPT-5
Context
400K
Input
$5.00
Latency
690 ms
Weekly
โ†“ 1.2%
Sonnet 4.5
Context
200K
Input
$3.00
Latency
820 ms
Weekly
โ†‘ 2.4%
Haiku 4.5
Context
200K
Input
$0.80
Latency
420 ms
Weekly
โ†‘ 4.8%

fig. 01 ยท plays once, replay from the corner

Installation

Install manually
npx shadcn@latest add "@assistant-ui/elements-data-table"
First time? Set up a runtime

Runtime components read their state from an assistant-ui runtime. Add one to an existing project:

npx assistant-ui@latest init

Then wrap your app in a runtime provider:

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/ai-sdk";

export default function App() {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({ api: "/api/chat" }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* your components */}
    </AssistantRuntimeProvider>
  );
}

The installation guide covers new projects, templates, and API routes.

DataTable turns a { columns, rows } result into an accessible comparison with locale-aware values, sorting, and a card view when its container gets narrow. The data source decides the columns, while the element owns the format, order, and responsive presentation.

Getting started

Use the same result shape your backend tool returns. The renderer stays small because the tool result already describes both the headings and values.

Render the result

components/assistant-ui/elements/compare-models-tool-ui.tsx
"use client";

import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
import { DataTable, type DataTableColumn, type DataTableRow } from "@/components/assistant-ui/elements/data-table";

type CompareModelsResult = {
  columns: readonly DataTableColumn[];
  rows: readonly DataTableRow[];
};

export const CompareModelsToolUI: ToolCallMessagePartComponent<
  { models: string[] },
  CompareModelsResult
> = ({ result }) =>
  result ? (
    <DataTable
      columns={result.columns}
      rows={result.rows}
      rowKey="id"
      caption="Model comparison"
    />
  ) : null;

Register the backend tool

app/toolkit.ts
import { defineToolkit } from "@assistant-ui/react";
import { z } from "zod";
import { CompareModelsToolUI } from "@/components/assistant-ui/elements/compare-models-tool-ui";

export const toolkit = defineToolkit({
  compare_models: {
    type: "backend",
    parameters: z.object({ models: z.array(z.string()) }),
    render: CompareModelsToolUI,
  },
});

The backend result is { columns, rows }. Keep values primitive or arrays of primitive values so the renderer can format and sort them predictably.

Anatomy

<div data-slot="data-table">
  <table>{/* column headers and sortable rows */}</table>
  <div role="list">{/* compact cards for narrow containers */}</div>
</div>

The root is a container. At @md the semantic table is visible; below it, each row becomes a card with the primary column as its title and the remaining fields in a definition list. Both layouts are rendered so CSS can choose without changing the data or its sort order.

The table has one hairline below its headers. Rows use a quiet hover fill instead of dividers. A supplied caption is visually hidden and gives the table a screen-reader name. Each sortable header cycles ascending, descending, then unsorted, and announces the new order in a polite live region. Only the sorted header carries aria-sort, and each sort button is named after its column, such as Sort by Latency.

Examples

Format values

format uses Intl with locale, which defaults to "en-US". Numeric formats end-align automatically. Empty values show muted none and sort last.

<DataTable
  locale="en-GB"
  relativeTo={Date.parse("2026-09-26T12:00:00Z")}
  columns={[
    { key: "revenue", label: "Revenue", format: { kind: "currency", currency: "GBP", compact: true } },
    { key: "conversion", label: "Conversion", format: { kind: "percent", decimals: 1 } },
    { key: "change", label: "Change", format: { kind: "delta", decimals: 1, unit: "%" } },
    { key: "updated", label: "Updated", format: { kind: "date", style: "relative" } },
    { key: "status", label: "Status", format: { kind: "badge", tones: { live: "success", review: "warning" } } },
    { key: "tags", label: "Tags", format: { kind: "list", max: 2 } },
  ]}
  rows={report.rows}
/>

Use basis: "unit" when a percentage arrives as 12 rather than 0.12. A link format reads its href from the cell or hrefKey, and only links allowed by safeHref become anchors. External links open with noopener noreferrer.

When relativeTo is absent, relative dates appear as absolute dates in UTC during server rendering and hydration. After mount, they switch to relative text using the client's clock. Pass relativeTo when a shared reference time matters. Date strings must name a real calendar date; impossible values stay as their raw text.

Own the sorting

Without sort, DataTable keeps the current sort internally. Pass sort and onSortChange to control it. null means the source row order. Numbers, booleans, ISO dates, lists, and strings each get an appropriate comparison. Empty values stay at the end in either direction.

<DataTable columns={columns} rows={rows} sort={sort} onSortChange={setSort} />

Fit a narrow container

Set one column to priority: "primary" to make it the card title. If no column is primary, the first column is used. Secondary values become dt and dd pairs, preserving labels when the headers are hidden.

<DataTable
  columns={[
    { key: "name", label: "Name", priority: "primary" },
    { key: "latency", label: "Latency", format: { kind: "number", unit: "ms" } },
    { key: "owner", label: "Owner" },
  ]}
  rows={rows}
/>

API reference

Runtime DataTable

PropTypeDefaultDescription
columnsDataTableColumn[]requiredBackend-provided column definitions.
rowsDataTableRow[]requiredBackend-provided values for each row.
rowKeystringRow field used as the React key.
captionstringScreen-reader caption for the table.
localestring"en-US"Locale used by all Intl formatting and string sorting.

Standalone DataTable

Runtime DataTableColumn

FieldTypeDescription
keystringRow property to read.
labelstringHeader and compact-card label.
formatDataTableFormatValue presentation rule.
sortablebooleanEnables header sorting. Defaults to true.
priority"primary" | "secondary"Primary values lead the compact card.

Standalone DataTableColumn

Runtime DataTableFormat

KindValueDescription
textstring, number, boolean, listPlain text.
number, currency, percent, deltanumberLocale-aware numeric values.
datevalid ISO calendar string or epoch millisecondsDate, date-time, or relative date. Impossible calendar dates stay as raw text.
booleanbooleanConfigurable true and false labels.
linkstringSafe URL from the value or hrefKey.
badge, liststring or listTone chip or limited list of chips.

Standalone DataTableFormat