Data table
A tool's rows and columns with formatting, sorting, and a card layout in narrow columns.
| GPT-5 | 400K | $5.00 | 690 ms | โ 1.2% |
| Sonnet 4.5 | 200K | $3.00 | 820 ms | โ 2.4% |
| Haiku 4.5 | 200K | $0.80 | 420 ms | โ 4.8% |
- Context
- 400K
- Input
- $5.00
- Latency
- 690 ms
- Weekly
- โ 1.2%
- Context
- 200K
- Input
- $3.00
- Latency
- 820 ms
- Weekly
- โ 2.4%
- Context
- 200K
- Input
- $0.80
- Latency
- 420 ms
- Weekly
- โ 4.8%
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 initThen 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.
npx shadcn@latest add "@assistant-ui/elements-data-table"Props-driven: no runtime or provider required.
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
"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
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.
Pass the same payload yourself. defaultSort gives an initial order, and sort with onSortChange lets your application own that state.
"use client";
import { useState } from "react";
import { DataTable, type DataTableColumn, type DataTableSort } from "@/components/assistant-ui/elements/data-table";
const columns = [
{ key: "name", label: "Model", priority: "primary" },
{ key: "context", label: "Context", format: { kind: "number", compact: true } },
{ key: "input", label: "Input", format: { kind: "currency", currency: "USD" } },
] as const satisfies readonly DataTableColumn[];
const rows = [
{ id: "sonnet", name: "Sonnet 4.5", context: 200_000, input: 3 },
{ id: "gpt", name: "GPT-5", context: 400_000, input: 5 },
];
export function ModelComparison() {
const [sort, setSort] = useState<DataTableSort | null>({
key: "context",
direction: "desc",
});
return <DataTable columns={columns} rows={rows} rowKey="id" sort={sort} onSortChange={setSort} caption="Model comparison" />;
}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
| Prop | Type | Default | Description |
|---|---|---|---|
columns | DataTableColumn[] | required | Backend-provided column definitions. |
rows | DataTableRow[] | required | Backend-provided values for each row. |
rowKey | string | Row field used as the React key. | |
caption | string | Screen-reader caption for the table. | |
locale | string | "en-US" | Locale used by all Intl formatting and string sorting. |
Standalone DataTable
| Prop | Type | Default | Description |
|---|---|---|---|
columns | readonly DataTableColumn[] | required | Ordered table columns. |
rows | readonly DataTableRow[] | required | Source rows. |
rowKey | string | index | Field used for row keys when present. |
caption | string | Visually hidden table caption. | |
defaultSort | { key, direction } | Initial uncontrolled sort. | |
sort | { key, direction } | null | Controlled sort. null restores source order. | |
onSortChange | (sort) => void | Called as a header cycles through sort states. | |
locale | string | "en-US" | Formatting and collator locale. |
emptyMessage | string | "No rows" | Message in an empty table or card list. |
relativeTo | number | absolute UTC dates until mount, then the client's clock | Epoch milliseconds for relative dates. Provide one for a shared reference time. |
className | string | Extra classes merged onto the root. |
Runtime DataTableColumn
| Field | Type | Description |
|---|---|---|
key | string | Row property to read. |
label | string | Header and compact-card label. |
format | DataTableFormat | Value presentation rule. |
sortable | boolean | Enables header sorting. Defaults to true. |
priority | "primary" | "secondary" | Primary values lead the compact card. |
Standalone DataTableColumn
| Field | Type | Description |
|---|---|---|
key | string | Row property to read. |
label | string | Header and card label. |
format | DataTableFormat | Formatting rule. Defaults to text. |
align | "start" | "end" | Cell alignment. Numeric formats default to end. |
sortable | boolean | Header interactivity. Defaults to true. |
priority | "primary" | "secondary" | Card-title choice. |
width | string | Optional desktop table column width. |
Runtime DataTableFormat
| Kind | Value | Description |
|---|---|---|
text | string, number, boolean, list | Plain text. |
number, currency, percent, delta | number | Locale-aware numeric values. |
date | valid ISO calendar string or epoch milliseconds | Date, date-time, or relative date. Impossible calendar dates stay as raw text. |
boolean | boolean | Configurable true and false labels. |
link | string | Safe URL from the value or hrefKey. |
badge, list | string or list | Tone chip or limited list of chips. |
Standalone DataTableFormat
| Kind | Options | Description |
|---|---|---|
text | Plain text. | |
number | decimals, compact, unit | Number with an optional compact suffix or unit. |
currency | currency, decimals, compact | Currency formatted by Intl.NumberFormat. |
percent | decimals, basis | Fractional input by default, or whole-unit input. |
delta | decimals, unit, upIsGood | The size of the change behind an up or down arrow, colored by whether that direction is good. |
date | style | "date", "datetime", or "relative". |
boolean | trueLabel, falseLabel | Display labels for boolean values. |
link | hrefKey | Safe href field when the display value is not the URL. |
badge | tones | Per-value neutral, success, warning, or danger tone. |
list | max | Maximum visible chips before a +N count. |