Skip to content
hightouchUI

Design system

7f8e26a

Approval card

Open in Figma

Approval card asks the user to approve an agent's action before the agent takes it. It lists what will change, and the user approves, requests changes, or rejects. The decision stays in the chat.

Example

Usage

Approval

The card renders reject, request changes, and approve actions, quietest first with the primary last on the right. Each button appears only when its callback is provided, so a card that can only be approved or rejected omits onRequestChanges. Request changes only fires its callback: the card collects no notes, so ask for them in the chat composer. Set status="submitting" while the decision is being applied, then resolve to approved, changes-requested, or rejected: the actions swap for the result line under the title.

Reviewing changes

changes lists the edits under review, one row each: a tinted add / change / remove marker, the item as a chip, with an optional icon for its type (tinted with iconColor), then the change itself in detail. The kind is announced to screen readers, so the detail needn't repeat it. It's plain data, so an agent can emit it directly. children remains the slot for anything else under review, such as a query or a plan, and renders below the changes.

Surface

The card background color takes the opposite of the surface it sits on, so it stands out: gray on a white background, white on a gray one. It reads the surface from the nearest Box, Row, or Column with a background token. When the parent's background is something else, such as a raw color or a plain element, set surface on a wrapping Box. Inputs in children follow the card's surface, not the chat's.

Statuses

status drives the badge, the controls, and the footer. pending is the only interactive state; submitting shows progress on the acting button and disables the other actions; approved, changes-requested, and rejected collapse the card to its outcome: the changes, custom content, and actions unmount. Pass result to say what the agent actually did. Without it a resolved card is just its header, since the badge already reports the status.

Disabled

Set isDisabled on a card the conversation has moved past, such as one the user answered by typing in chat instead. The actions and the dismiss button stop responding, and the card keeps its changes on screen. Disable any controls you pass in children yourself.

Guidelines

When to use

  • In an agent chat, when the agent needs sign-off before a consequential or irreversible action: publishing, sending, spending.
  • When the agent proposes edits the user should see itemized before they apply.
  • When the outcome of the decision should stay visible in the conversation after it's made.

When not to use

  • For confirmations outside a chat surface, use a confirmation dialog. A modal is the right interruption for page-level actions.
  • When the agent needs answers rather than a yes or no, such as a scope choice or a set of constraints, use an ask user card instead of chaining approvals.
  • For passive progress or status reporting with no decision to make, use an alert or a badge.

Content

  • Write the title as the decision being asked for, in one short line. The header truncates it and shows the full text in a tooltip.
  • Put what will happen and why in description; keep result to what the agent did with the decision.

Accessibility

  • The card is a group named by its title, and the changes are an announced list: each row reads its kind, its item, and its detail.
  • After an action resolves the card, focus moves to the card if it was still in the card.
  • The result is announced when it arrives.

Props

Inherits margin props.

ApprovalCard

NameDefaultDescription
result

—

ReactNodeWhat the agent did once the card is resolved. Omit it and the card collapses to its header, since the badge already reports the outcome.
isDisabledfalsebooleanDisables every control, for a card the conversation has moved past.
onDismiss

—

() => voidCallback for the header's dismiss button. The button renders only when provided.
title"Approval required"stringCard title, also the card's accessible name. Keep it to one short line: the header truncates it, with the full text in a tooltip.
description

—

ReactNodeSupporting copy below the title.
changes

—

ApprovalChange[]The edits under review, one row each. Plain data, so an agent can emit them directly.
children

—

ReactNodeCustom content between the description and the actions, e.g. the query or plan being reviewed. Renders below changes.
status"pending""pending" | "submitting" | "approved" | "rejected" | "changes-requested"Where the request stands. pending is interactive; submitting puts a spinner on the action that was pressed (the primary action when none was) and disables the others; approved, changes-requested, and rejected render the outcome.
onApprove

—

() => voidCallback for the Approve action. The button renders only when provided.
onReject

—

() => voidCallback for the Reject action. The button renders only when provided.
onRequestChanges

—

() => voidCallback for the Request changes action. The button renders only when provided. The card collects no notes, so ask for them elsewhere, e.g. in the chat composer.
approveLabel"Approve"stringLabel of the Approve action.
rejectLabel"Reject"stringLabel of the Reject action.
requestChangesLabel"Request changes"stringLabel of the Request changes action.

ApprovalChange

NameDefaultDescription
kind

—

"add" | "change" | "remove"What happens to the item. Drives the marker, and is announced to screen readers.
label

—

stringThe item being changed, shown as a chip.
icon

—

ComponentType<SVGAttributes<SVGElement>>Icon for the item's type, shown in the chip.
iconColor"icon.secondary"ColorProps["color"]Color of icon, e.g. the item type's color.
detail

—

stringThe change itself, e.g. "greater than 0.5 (was 0.3)".