Response Actions

Lightweight decision buttons for tool outputs.

Examples

The following examples show Response Actions in context. In each, the AI has done its work and is waiting for human approval before proceeding.

DataTable: Flagged Expenses

AI detected anomalies in recent expenses. The user must approve or escalate before reimbursement proceeds.

Table data shown as expandable cards. Each card represents one row. Columns: Merchant, Amount, Flag.
Delta Airlines
Amount:$847.00
Flag:Possible duplicate
The Capital Grille
Amount:$312.50
Flag:Over policy limit
Uber
Amount:$45.20
Flag:Missing receipt
import { DataTable } from "@/components/tool-ui/data-table";// AI flagged these expenses as anomalies. User decides before processing.<DataTable  id="flagged-expenses"  rowIdKey="id"  columns={[    { key: "merchant", label: "Merchant", priority: "primary" },    {      key: "amount",      label: "Amount",      align: "right",      format: { kind: "currency", currency: "USD" },    },    {      key: "flag",      label: "Flag",      format: {        kind: "status",        statusMap: {          duplicate: { tone: "warning", label: "Possible duplicate" },          policy: { tone: "danger", label: "Over policy limit" },          missing: { tone: "info", label: "Missing receipt" },        },      },    },  ]}  data={[    { id: "1", merchant: "Delta Airlines", amount: 847.00, flag: "duplicate" },    { id: "2", merchant: "The Capital Grille", amount: 312.50, flag: "policy" },    { id: "3", merchant: "Uber", amount: 45.20, flag: "missing" },  ]}  responseActions={{    items: [      { id: "reject", label: "Reject All", variant: "destructive", confirmLabel: "Confirm" },      { id: "approve", label: "Approve All", variant: "default", confirmLabel: "Confirm" },    ],    align: "right",  }}  onResponseAction={(id) => {    if (id === "approve") submitExpenses();    if (id === "reject") returnToSubmitter();  }}/>

Image: Generated Hero Image

AI generated a hero image for the blog post. The user must approve before it goes live.

Abstract visualization of neural networks with glowing nodes
Generated: AI Infrastructure
import { Image } from "@/components/tool-ui/image";// AI generated this image. User approves before publishing.<Image  id="generated-hero"  assetId="gen-img-7f3a"  src="https://images.unsplash.com/photo-1620712943543-bcc4688e7485"  alt="Abstract visualization of neural networks with glowing nodes"  title="Generated: AI Infrastructure"  description="Style: technical illustration. Prompt: 'neural network visualization...'"  ratio="16:9"  maxWidth="420px"  responseActions={[    { id: "regenerate", label: "Try Again", variant: "secondary" },    { id: "edit", label: "Edit Prompt", variant: "outline" },    { id: "use", label: "Use This", variant: "default" },  ]}  onResponseAction={(id) => {    if (id === "use") attachToPost(assetId);    if (id === "regenerate") generateNewImage();    if (id === "edit") openPromptEditor();  }}/>

XPost: Draft Awaiting Publish

AI drafted a post based on the user's notes. The user reviews and decides whether to publish, schedule, or revise.

Acme Dev avatar
Acme Dev@acme_dev

We just hit 10,000 stars on GitHub! Huge thanks to everyone who contributed, filed issues, and spread the word. Open source is a team sport.

import { XPost } from "@/components/tool-ui/x-post";// AI drafted this post. User approves before it goes live.<XPost  post={{    id: "draft-post-1",    author: {      name: "Acme Dev",      handle: "acme_dev",      avatarUrl: "https://api.dicebear.com/7.x/shapes/svg?seed=acme",      verified: true,    },    text: "We just hit 10,000 stars on GitHub! Huge thanks to everyone...",  }}  responseActions={[    { id: "edit", label: "Edit", variant: "secondary" },    { id: "schedule", label: "Schedule", variant: "outline" },    { id: "post", label: "Post Now", variant: "default", confirmLabel: "Confirm" },  ]}  onResponseAction={(id) => {    if (id === "post") publishImmediately();    if (id === "schedule") openScheduler();    if (id === "edit") openEditor();  }}/>

Action Shape

responseActions accepts either an array of actions or an object with layout config:

// Array formresponseActions={[  { id: "approve", label: "Approve", variant: "default" },  { id: "reject", label: "Reject", variant: "destructive", confirmLabel: "Confirm" },]}// Config form with alignmentresponseActions={{  items: [    { id: "export", label: "Export", variant: "secondary" },    { id: "sync", label: "Sync", variant: "default", confirmLabel: "Confirm" },  ],  align: "right",      // default "right"  confirmTimeout: 3000 // default 3000ms}}

Action Fields

Prop

Type

Handlers

Prop

Type

Serialization

Use SerializableAction when actions come from tool/LLM output. Runtime code can add React icons and handlers on top.

// Tool output (serializable)const toolOutput = {  responseActions: [    { id: "open", label: "Open", variant: "default" },    { id: "save", label: "Save", variant: "secondary" },  ],};// Runtime rendering (can add icons, handlers)<LinkPreview  {...toolOutput}  onResponseAction={(id) => {    if (id === "open") window.open(url, "_blank");    if (id === "save") saveToLibrary(url);  }}/>;

Positioning

Actions render in array order, left to right.

// Visual order: Cancel | Preview | DeployresponseActions={[  { id: "cancel", label: "Cancel", variant: "ghost" },  { id: "preview", label: "Preview", variant: "secondary" },  { id: "deploy", label: "Deploy", variant: "default" },]}

Tab order matches visual order, so keyboard users navigate left-to-right as expected.

Design Guidance

  • Default to 2–3 CTAs tied directly to the content above; avoid toolbar sprawl
  • Use confirmLabel for destructive or high-impact actions
  • Keep confirmTimeout short (default 3000ms) to avoid stale confirm states
  • ActionButtons are responsive by default and stack on narrow containers
  • For richer controls, prefer dedicated components (Option List, filters) and keep this strip focused on decisions
Response Actions | Tool UI