| Webhook retries inconsistent | High | Nov 13, 2025 | $527.50 |
| Payment failing on checkout | High | Nov 11, 2025 | $129.99 |
| Billing UI alignment bug | Medium | Nov 10, 2025 | $42.00 |
| Export CSV missing headers | Low | Nov 8, 2025 | $0.00 |
import { DataTable, type Column } from "@/components/tool-ui/data-table";type Row = { id: string; issue: string; priority: "high" | "medium" | "low"; createdAt: string; amount: number;};const columns: Column<Row>[] = [ { key: "issue", label: "Issue", priority: "primary", truncate: true }, { key: "priority", label: "Urgency", format: { kind: "status", statusMap: { high: { tone: "danger", label: "High" }, medium: { tone: "warning", label: "Medium" }, low: { tone: "neutral", label: "Low" }, }, }, }, { key: "createdAt", label: "Created", format: { kind: "date", dateFormat: "relative" } }, { key: "amount", label: "Amount", align: "right", format: { kind: "currency", currency: "USD", decimals: 2 } },];const data: Row[] = [ { id: "1", issue: "Payment failing on checkout", priority: "high", createdAt: "2025-11-11T09:24:00Z", amount: 129.99, }, { id: "2", issue: "Billing UI alignment bug", priority: "medium", createdAt: "2025-11-10T17:03:00Z", amount: 42, }, { id: "3", issue: "Export CSV missing headers", priority: "low", createdAt: "2025-11-08T08:00:00Z", amount: 0, }, { id: "4", issue: "Webhook retries inconsistent", priority: "high", createdAt: "2025-11-13T22:15:00Z", amount: 527.5, },];export default function Example() { return ( <DataTable rowIdKey="id" columns={columns} data={data} defaultSort={{ by: "createdAt", direction: "desc" }} /> );}Automatically switches from table to cards on mobile
Tri-state column sorting: none → ascending → descending
Currency, dates, links, badges, and more via config
Add actionable buttons below the table
Copy components/tool-ui/data-table and the shared directory into your project. The shared folder contains utilities used by all Tool UI components. The tool-ui directory should sit alongside your shadcn ui directory.
This component requires the following shadcn/ui components:
pnpm dlx shadcn@latest add accordion badge button dropdown-menu table tooltipDefine a tool that returns table data, then render with the DataTable component.
// Backend toolimport { tool, jsonSchema } from "ai";const searchProducts = tool({ description: "Search products and return results in a table", inputSchema: jsonSchema<{ query: string }>({ type: "object", properties: { query: { type: "string" } }, required: ["query"], additionalProperties: false, }), async execute({ query }) { const results = await db.products.search(query); return { id: "data-table-search-products", columns: [ { key: "name", label: "Product", priority: "primary" }, { key: "price", label: "Price", format: { kind: "currency", currency: "USD" }, }, { key: "stock", label: "Stock", align: "right" }, ], data: results.map((p) => ({ id: p.id, name: p.name, price: p.price, stock: p.stock, })), }; },});// Frontend with assistant-uiimport { makeAssistantToolUI } from "@assistant-ui/react";import { DataTable, DataTableErrorBoundary, parseSerializableDataTable,} from "@/components/tool-ui/data-table";export const SearchProductsUI = makeAssistantToolUI({ toolName: "searchProducts", render: ({ result }) => { // Tool outputs stream in; `result` will be `undefined` until the tool resolves. if (result === undefined) { return ( <div className="bg-card/60 text-muted-foreground w-full max-w-xl rounded-2xl border px-5 py-4 text-sm shadow-xs"> Loading table… </div> ); } const props = parseSerializableDataTable(result); return ( <DataTableErrorBoundary> <DataTable rowIdKey="id" {...props} /> </DataTableErrorBoundary> ); },});Prop
Type
Prop
Type
Prop
Type
Prop
Type
All visual formatting for data stays declarative via columns[i].format. Keep row values as primitives and describe presentation through format configs.
Prop
Type
<table> markup with proper header associationsimport { DataTable, type Column } from "@/components/tool-ui/data-table";
type Row = {
id: string;
issue: string;
priority: "high" | "medium" | "low";
createdAt: string;
amount: number;
};
const columns: Column<Row>[] = [
{ key: "issue", label: "Issue", priority: "primary", truncate: true },
{
key: "priority",
label: "Urgency",
format: {
kind: "status",
statusMap: {
high: { tone: "danger", label: "High" },
medium: { tone: "warning", label: "Medium" },
low: { tone: "neutral", label: "Low" },
},
},
},
{ key: "createdAt", label: "Created", format: { kind: "date", dateFormat: "relative" } },
{ key: "amount", label: "Amount", align: "right", format: { kind: "currency", currency: "USD", decimals: 2 } },
];
const data: Row[] = [
{
id: "1",
issue: "Payment failing on checkout",
priority: "high",
createdAt: "2025-11-11T09:24:00Z",
amount: 129.99,
},
{
id: "2",
issue: "Billing UI alignment bug",
priority: "medium",
createdAt: "2025-11-10T17:03:00Z",
amount: 42,
},
{
id: "3",
issue: "Export CSV missing headers",
priority: "low",
createdAt: "2025-11-08T08:00:00Z",
amount: 0,
},
{
id: "4",
issue: "Webhook retries inconsistent",
priority: "high",
createdAt: "2025-11-13T22:15:00Z",
amount: 527.5,
},
];
export default function Example() {
return (
<DataTable
rowIdKey="id"
columns={columns}
data={data}
defaultSort={{ by: "createdAt", direction: "desc" }}
/>
);
}pnpm dlx shadcn@latest add accordion badge button dropdown-menu table tooltip// Backend tool
import { tool, jsonSchema } from "ai";
const searchProducts = tool({
description: "Search products and return results in a table",
inputSchema: jsonSchema<{ query: string }>({
type: "object",
properties: { query: { type: "string" } },
required: ["query"],
additionalProperties: false,
}),
async execute({ query }) {
const results = await db.products.search(query);
return {
id: "data-table-search-products",
columns: [
{ key: "name", label: "Product", priority: "primary" },
{
key: "price",
label: "Price",
format: { kind: "currency", currency: "USD" },
},
{ key: "stock", label: "Stock", align: "right" },
],
data: results.map((p) => ({
id: p.id,
name: p.name,
price: p.price,
stock: p.stock,
})),
};
},
});
// Frontend with assistant-ui
import { makeAssistantToolUI } from "@assistant-ui/react";
import {
DataTable,
DataTableErrorBoundary,
parseSerializableDataTable,
} from "@/components/tool-ui/data-table";
export const SearchProductsUI = makeAssistantToolUI({
toolName: "searchProducts",
render: ({ result }) => {
// Tool outputs stream in; `result` will be `undefined` until the tool resolves.
if (result === undefined) {
return (
<div className="bg-card/60 text-muted-foreground w-full max-w-xl rounded-2xl border px-5 py-4 text-sm shadow-xs">
Loading table…
</div>
);
}
const props = parseSerializableDataTable(result);
return (
<DataTableErrorBoundary>
<DataTable rowIdKey="id" {...props} />
</DataTableErrorBoundary>
);
},
});