# Action Preview

> Pattern 04 · Approving actions · part of Agent Consent Patterns.
> Human page: https://agentconsent.dev/patterns/action-preview/

Show exactly what the agent is about to do (recipient, content, amount) and collect an explicit decision before anything executes.

## Problem

An agent announces it's ready to act: send the email, file the ticket, place the order. A generic confirmation ("Assistant wants to send an email. Allow?") asks the user to approve a *category* of action while the agent performs a *specific* one. That gap is where every consequential mistake lives: the wrong recipient, the hallucinated figure, the draft tone that was never meant to leave the room. Users can't catch what they were never shown.

## Solution

Before execution, render the action itself as structured facts: each consequential parameter as a labeled field with its verbatim value, long content available for inspection, and approve/reject controls that state the action in their labels. The preview is the contract: what's on the card is exactly what approval executes, and nothing else.

> **Live demo** — an interactive Action Preview demo runs on the page: https://agentconsent.dev/patterns/action-preview/

The demo is the `ActionPreview` component from `@agentconsent/react`, headless primitives with the default theme. Toggle the `consequence` prop to see the approve action re-weight itself for an irreversible send.

## Anatomy

> **Anatomy** — a labeled breakdown of the Action Preview component's parts is shown on the interactive page: https://agentconsent.dev/patterns/action-preview/

## When to use it

- **Consequential, externally visible actions.** Messages sent, money moved, records created or changed in systems other people see.
- **When parameters came from inference.** If the agent chose the recipient, amount, or wording itself (rather than echoing explicit user input), the user is reviewing the agent's judgment, not just its permission.
- **First executions of a new kind of action**, before any standing authority (see Consent Memory) has been established.

## When not to use it

- **Trivially reversible, low-stakes actions.** Reading, searching, drafting without sending. Previewing these breeds approval fatigue and trains users to click through the previews that matter.
- **High-volume queues.** Ten previews in a row is not consent, it's a grind: reach for Batch Approval instead.
- **As a liability shield.** If the real goal is "the user technically clicked yes," the pattern is being used against its purpose. A preview the user can't meaningfully evaluate is an anti-pattern regardless of its anatomy.

## Real-world examples

- **Claude Code.** Every mutating tool call is rendered concretely before it runs (the exact shell command, the exact file diff) with approve/deny as the gate. The preview *is* the contract: what was shown is what executes.
- **ChatGPT agent mode.** Pauses before consequential actions (purchases, form submissions, sending) and asks for explicit confirmation showing what it is about to do, rather than approving the category of action.
- **AI email assistants.** Gemini in Gmail and most agentic email clients stage the drafted reply in full for review; the send happens only on the user's own click. The draft-then-review loop is this pattern applied to the most common consequential action there is.

*Annotated screenshots of these flows are being collected. Products are credited, annotations follow the site-wide callout conventions, and any screenshot is removed on request; see [About](/about/).*

## Accessibility

- **Inline mode** renders a `role="group"` labeled by the action title, so screen readers announce the full context ("Send email to Dana Ito, group") when entering the card. It never steals focus: an agent proposing an action must not interrupt what the user is doing.
- **Modal mode** (`asModal`) is a Radix `AlertDialog`: focus is trapped, `role="alertdialog"` is named by the title, and initial focus lands on the **reject** action. Approval must always be a deliberate movement, never the resting default.
- **Escape rejects.** In modal mode, dismissal is an explicit "no", routed through `onReject` like the button. There is no third "just make it go away" state that leaves the action pending.
- Field rows are a definition list (`<dl>`), so label–value pairs stay associated for assistive tech.
- The content disclosure is a real `<button>` with `aria-expanded` and `aria-controls`; collapsed content is `hidden`, not clipped.
- Consequence weighting never relies on color alone: the irreversible variant changes the button label ("Send now"), not just its palette.

## Anti-patterns

- **The category confirmation.** "Allow Assistant to send email?" approves a class of action and shows none of its facts. This is the pattern's reason to exist; don't rebuild it with nicer typography.
- **Summarized values.** "To: Dana and 2 others" hides exactly the parameter most worth checking. Truncate long values behind a disclosure, never behind a paraphrase.
- **Default-focused approve.** Enter-to-approve as the resting state turns the preview into a formality. The cheapest gesture must be the cautious one.
- **The mutating preview.** If the agent revises the action after approval, even trivially, the contract is broken. Any change to the previewed facts requires a new preview.
- **Scary rejection.** Making "don't do this" feel destructive ("Discard agent's work?") punishes caution and biases users toward approval.

## Code

```tsx
import { ActionPreview } from "@agentconsent/react";
import "@agentconsent/react/theme.css";

<ActionPreview.Root
  consequence="reversible"        // or "irreversible"
  onApprove={() => execute(action)}
  onReject={() => cancel(action)}
>
  <ActionPreview.Header>
    <ActionPreview.Icon>✉</ActionPreview.Icon>
    <ActionPreview.Title>Send email to Dana Ito</ActionPreview.Title>
  </ActionPreview.Header>

  <ActionPreview.Fields>
    <ActionPreview.Field label="To">dana@northwindcap.com</ActionPreview.Field>
    <ActionPreview.Field label="Subject">Q3 board deck, final</ActionPreview.Field>
  </ActionPreview.Fields>

  <ActionPreview.Content label="Message body">
    {action.body}
  </ActionPreview.Content>

  <ActionPreview.Source agent="Inbox Assistant" authority="task: board prep" />

  <ActionPreview.Actions>
    <ActionPreview.Reject>Don't send</ActionPreview.Reject>
    <ActionPreview.Approve>Send email</ActionPreview.Approve>
  </ActionPreview.Actions>
</ActionPreview.Root>
```

Pass `asModal` with `open`/`onOpenChange` to render as a focus-trapped alert dialog instead of an inline card. All parts are unstyled primitives with `data-acp` attributes; skip `theme.css` and style them yourself, or override the `--acp-*` tokens to retheme.
