Editable Table
Records the user corrects in place, like a small spreadsheet. The agent gets back only what changed.
Pattern · Shares patterns with Data Table, Change Preview
≈ 1,450 tokens per use (tool 822 · guidance 301 · typical payload 327)How token costs are estimated
Example: Editable Table
Let me clean up our design tool list before renewals.
Design stack
Arrow keys move between cells. Enter or typing edits a cell, Escape cancels an edit or, on a selected cell, clears the selection, Delete clears the value.
| Figma | Design | figma.com | Mar 12, 2026 | |
| Linear | Issue tracking | linear.app | Jan 4, 2026 | |
| Vercel | Hosting | vercel.com | Aug 21, 2026 | |
| Framer | Website | framer.com | May 2, 2026 | |
| Loom | Video | loom.com | Nov 18, 2026 |
When to use
- The user wants to fix, fill in or update several records with the same fields: "let me clean up our tool list", "update the renewal dates".
- The user will add or remove a few rows and change values across them before anything is saved.
When not to use
- The user wants to read, sort or act on records in bulk without changing values. Use a Data Table.
- You're proposing specific changes for the user to approve. Use a Change Preview with before and after values.
- One record with many fields. Use a Generated Form.
- Editing generated text. Use the Inline Editor.
How the agent should use it
- Every row comes from a tool result. Never invent records or fill blanks with guesses; leave a missing value
null. - Keep to the 2–6 columns the user will edit, with the identifying column first.
- Give a
selectcolumn every allowed value asoptions, so the user can't type one the system rejects. - Mark computed or system fields
readOnly. - The result is intent, not a write. Apply the changes with your tools, then report what was saved.
Interaction
- Click a cell to select it; its address (A1, B3) shows in the footer. Click it again, double-click, press Enter or just start typing to edit.
- Enter saves and moves down, Tab moves right, Escape cancels an edit. Escape on a selected cell, like a click outside the cells, lets it go. Delete or Backspace clears the selected cell's value.
- Clicking anywhere outside the cells (the page, the title, the footer) lets the selected cell go, saving an open edit first. A value the editor rejects keeps its cell for one click so the reason shows; the next click elsewhere drops it and the cell keeps its old value.
selectanddatecells show a chevron once selected, since that click opens their options or the date picker. Clicking the cell while it's open closes it.- Column headers sort the table: ascending, descending, then the agent's order.
- Row checkboxes select rows to delete. Add row appends an empty row and starts editing it.
- Undo and redo cover every edit, addition and deletion, from the header buttons or Ctrl/⌘ Z and Shift Ctrl/⌘ Z.
- Save changes is enabled once something changed. After saving, the table locks and says what was saved.
Design
- Each header names its type with a 14px icon (text, number, select, link, date) so the user knows what a cell accepts before editing it.
selectvalues are tags coloured by their place inoptions: the first five take the series colours (tinted fill, text in the same hue), the rest stay blue. An option keeps its colour however the rows change, and the label always shows, so colour is never the only key.- A row's
logoshows as a 20px mark beside the first column, with a dark file for marks that would vanish on a dark card. Links show the domain, underlined. - The selected cell and the open editor carry the violet selection ring; a rejected value turns the ring red and the reason replaces the cell address in the footer.
- The selection and first columns stay put while the table scrolls sideways on narrow screens.
Accessibility
- The table is an ARIA grid with one tab stop: arrow keys move between cells, Left and Right wrapping to the previous or next row (Tab and Shift+Tab do the same while editing). Home and End go to the row's ends; pressed again there, they move to the previous or next row's first or last cell. With Ctrl or ⌘, they go to the table's corners. Page Up and Page Down move five rows up or down in the same column, stopping at the first and last rows.
- Every editor is named after its column and row ("Category, Figma"). Edits, deletions, undo and validation errors are announced in a polite live region.
- Empty cells read as "Empty"; read-only cells are marked
aria-readonly;selectanddatecells say they open a list or a dialog (aria-haspopup).
Installation
npx shadcn@latest add https://your-site.com/r/editable-table.json
Usage
import { tool } from "ai"
import { editableTableTool } from "@/components/genui/editable-table.schema"
const tools = {
// No execute(): the call is completed when the user saves.
[editableTableTool.name]: tool({
description: editableTableTool.description,
inputSchema: editableTableTool.inputSchema,
}),
}import { EditableTable } from "@/components/genui/editable-table"
function ToolPart({ part, addToolResult }) {
if (part.type !== "tool-edit_table") return null
return (
<EditableTable
{...part.input}
status={part.state === "input-streaming" ? "streaming" : undefined}
result={part.state === "output-available" ? part.output : undefined}
onResult={(result) =>
addToolResult({
tool: "edit_table",
toolCallId: part.toolCallId,
output: result,
})
}
/>
)
}The agent receives only what changed: the edited cells of each existing row, the cells of new rows, and the ids of deleted rows.
{
action: "save",
updated: [{ id: "tool_loom", cells: { category: "Video", renews: "2026-12-01" } }],
added: [{ cells: { tool: "Notion", category: "Design" } }],
deleted: ["tool_framer"],
}Restoring from history
Pass the stored result back as result. The component replays the saved edits onto the original rows, renders the table locked and never calls onResult again.
Schema
| Prop | Type | Notes |
|---|---|---|
title | string | What the records are, e.g. "Design stack" |
description | string? | Scope, e.g. the event date |
columns | Column[] | 1–6 columns |
rows | { id, cells, logo? }[] | Data-bound. Up to 100. cells maps column keys to a string, number or null. logo is { url, darkUrl?, alt? }, only when the data has one |
allowAdd | boolean | Default true |
allowDelete | boolean | Default true. Off hides the row checkboxes |
sort | { key, direction }? | Initial sort |
submitLabel | string? | Save button label. Default "Save changes" |
Column
| Field | Type | Notes |
|---|---|---|
key | string | Matches keys in cells |
label | string | Header text, 32 characters at most |
type | text | number | select | url | date | Default text. date takes YYYY-MM-DD |
options | string[]? | For select: up to 12 allowed values. The first five get their own tag colours |
readOnly | boolean? | The user can't change this column |
sortable | boolean | Default true |
Host props: status (streaming, ready or submitted), onResult, result, surface and className.