Data Table

Records the agent pulled from real data. Sort them, select some, and act in bulk.

Pattern

≈ 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

Sorts by this column. Alt with the left or right arrow moves the column.

Summer sale — carouselProspecting$1,8400.620.8%Active
Lookalike — video 15sProspecting$1,2600.811.1%Active
Brand story — staticAwareness$9400.940.6%Active
Gift guide — collectionProspecting$8901.181.4%Active
New arrivals — reelsProspecting$7201.421.9%Active
Free shipping — staticRetargeting$4102.102.4%Learning
Bundle offer — carouselRetargeting$5603.052.8%Active
Back in stock — dynamicRetargeting$1503.903.2%Active
Cart reminder — dynamicRetargeting$3804.203.6%Active
Loyalty — staticRetention$2105.604.1%Active

3 selected

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 flag only 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 pageSize is 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

app/api/chat/route.ts
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,
  }),
}
components/message.tsx
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

PropTypeNotes
titlestringWhat the table shows
descriptionstring?Source or scope, e.g. the account
columnsColumn[]1–8 columns
rowsRow[]Data-bound. Up to 200
selectablebooleanDefault false. Required for actions
actions{ id, label, variant? }[]Up to 3 bulk actions. variant: "destructive" for pauses and deletes
sort{ key, direction }Initial sort
pageSizenumber?Only for long tables: rows shown before Show more. By default every row is shown

Column

FieldTypeNotes
keystringMatches keys in cells
labelstringHeader text
typetext | number | currency | percent | date | statuspercent takes 12.5 to mean 12.5%. date takes an ISO string
currencystring?ISO 4217 code
decimalsnumber?Fixed decimals for numbers and percents
negativestring[]?status values to show as a negative badge. Other values show as plain text
sortablebooleanDefault true

Row

FieldTypeNotes
idstringData-bound. The record's real ID
cellsRecord<string, value>Data-bound
flagstring?Why this row matters
selectedboolean?Pre-select it

Host props: status (streaming, ready, submitted), onResult, result and className.

Source

On this page