Confirmation card
The safe pattern for actions an agent takes on the user's behalf: everything that will happen in plain rows, one explicit confirm, and a receipt afterwards. Confirm stays blocked while the summary streams or after the quote expires.
On this page
Every row that will happen, a held price with a countdown, and one explicit confirm that turns into a receipt.
Installation
Usage
import { ConfirmationCard } from "@/components/ui/chatcn/confirmation-card"
<ConfirmationCard
title="Book this flight?"
source="Requested by the travel agent"
details={[{ label: "Flight", value: "UA 1532, SFO to JFK" }]}
lineItems={[{ label: "Fare", amount: 412 }]}
total={{ amount: 455.81 }}
expiresAt={quote.expiresAt}
confirmLabel="Book flight"
onConfirm={async () => ({ reference: (await book(quote.id)).pnr })}
onCancel={() => tell("The user declined.")}
/>Examples
Every state
Streaming, confirmed, failed, expired and cancelled.
Safety
- Nothing runs until the user presses confirm. The agent cannot confirm on their behalf.
- Clicks in the first 600 ms after the card mounts are ignored (
armDelayMs), so a card that streams in under the pointer cannot be confirmed by a click meant for something else. - Confirm is disabled while the summary is still streaming, so nobody agrees to rows they have not seen.
- A held price counts down and expires; after that the card asks for a new quote instead of confirming a stale one.
sourcenames who asked, so agent-initiated actions stay attributable.
Props
| Prop | Type | Notes |
|---|---|---|
title, description, source | ReactNode | |
details | { label, value, detail? }[] | |
lineItems, total | Amounts in currency | |
note | ReactNode | Terms or refund policy |
confirmLabel, cancelLabel | string | Specific verbs |
onConfirm | () => void | Promise<void | { reference? }> | Reject to show the error |
onCancel | () => void | |
status, reference, error | Controlled status for restored cards | |
expiresAt | number | Epoch ms |
streaming, armDelayMs |
Accessibility
- The status (Needs your confirmation, Held for 9:42, Confirmed) is a live region. The countdown ticks once a second.
- Errors use an icon and words and say whether anything was charged.
Motion
- The status icon swaps with a 150 ms blur crossfade. The spinner sits on top of the confirm label, so the button keeps its width.