Data Table
Records the agent pulled from real data. Sort them, select some, and act in bulk.
≈ 1,604 tokens per use (tool 826 · guidance 238 · typical payload 540)How token costs are estimated
Example: Data Table
Which ads are underperforming this week?
Active ads, last 7 days
When to use
- You're showing several records with the same fields, and comparing across rows matters.
- The user may want to act on some of the rows: pause ads, email contacts, close tickets.
When not to use
- There are 1–2 records. Describe them in text, or use entity cards.
- There's one number to report. Answer in a sentence.
- The point is a trend over time. Use a chart.
- You're comparing 2–3 options across many attributes. Use a comparison.
How the agent should use it
- Every row comes from a tool result. Never invent, merge or round records.
- Keep to the 3–6 columns that answer the question, with the identifying column first.
- Lead with what matters. Sort so the important rows come first, add a short
flagonly when the reason isn't already visible in the columns, and pre-select the rows you recommend acting on. - An action is intent, not a write. When the user picks "Pause ads", follow up with a Change Preview before changing anything.
Interaction
- Column headers sort the table. Numeric columns start descending, text columns ascending.
- The header checkbox selects every row, including any hidden behind Show more when
pageSizeis set. - Rows can be selected while they're still streaming in. Actions wait for the complete payload.
- After an action, the table locks and shows what was requested.
Installation
npx shadcn@latest add https://your-site.com/r/data-table.json
Usage
import { tool } from "ai"
import { dataTableTool } from "@/components/genui/data-table.schema"
const tools = {
// No execute(): the call is completed by the user's action in the UI.
[dataTableTool.name]: tool({
description: dataTableTool.description,
inputSchema: dataTableTool.inputSchema,
}),
}import { DataTable } from "@/components/genui/data-table"
function ToolPart({ part, addToolResult }) {
if (part.type !== "tool-show_table") return null
return (
<DataTable
{...part.input}
status={part.state === "input-streaming" ? "streaming" : undefined}
result={part.state === "output-available" ? part.output : undefined}
onResult={(result) =>
addToolResult({
tool: "show_table",
toolCallId: part.toolCallId,
output: result,
})
}
/>
)
}For a display-only table (no actions), the agent doesn't need to wait for a result. In that case, you can give the tool an execute that returns immediately.
The agent receives:
{ action: "pause", rowIds: ["ad_3301", "ad_3302", "ad_3290"] }With LangGraph
Push it from a graph node with ui.push({ name: "show_table", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.
Restoring from history
When a conversation is reloaded, pass the stored result back as result. The component renders its finished state (the rows that were acted on) and never calls onResult again, so a decision can't be made twice. With the AI SDK, that's the tool call's output:
<DataTable
{...part.input}
result={part.state === "output-available" ? part.output : undefined}
/>Schema
| Prop | Type | Notes |
|---|---|---|
title | string | What the table shows |
description | string? | Source or scope, e.g. the account |
columns | Column[] | 1–8 columns |
rows | Row[] | Data-bound. Up to 200 |
selectable | boolean | Default false. Required for actions |
actions | { id, label, variant? }[] | Up to 3 bulk actions. variant: "destructive" for pauses and deletes |
sort | { key, direction } | Initial sort |
pageSize | number? | Only for long tables: rows shown before Show more. By default every row is shown |
Column
| Field | Type | Notes |
|---|---|---|
key | string | Matches keys in cells |
label | string | Header text |
type | text | number | currency | percent | date | status | percent takes 12.5 to mean 12.5%. date takes an ISO string |
currency | string? | ISO 4217 code |
decimals | number? | Fixed decimals for numbers and percents |
negative | string[]? | status values to show as a negative badge. Other values show as plain text |
sortable | boolean | Default true |
Row
| Field | Type | Notes |
|---|---|---|
id | string | Data-bound. The record's real ID |
cells | Record<string, value> | Data-bound |
flag | string? | Why this row matters |
selected | boolean? | Pre-select it |
Host props: status (streaming, ready, submitted), onResult, result and className.