# Approval Card

> The human-in-the-loop decision: what an agent wants to do, the exact payload, and the four answers A–D.

Component · AI · Pro kit · [Live page](https://ui.twentyfirst.media/pro/components/ApprovalCard/)

The human-in-the-loop decision: what an agent wants to do, the exact payload, and the four answers A–D.

Props: `title` (the action as one sentence), `text` (why), children (the payload, verbatim), `agent`, `project`, `family` ("money" | "infra" | "comms" | "marketing" | "apps" | "legal") or `familyLabel`, `age`, `escalation`, `trace` (node), `note`, `heading`, `onDecide(key, text)`, `decision` ({key, by, at, text}: renders the one-line record), `options` (override A–D; each {key, label, input}), `approveLabel` (relabels A), `compact`. Legacy `onApprove` / `onDeny` / `onEdit` and `state` still work.

- Always four answers in this order: **A** yes, as shown · **B** yes, with a change · **C** no, discard · **D** different instruction. B and D open a text field; ⌘/Ctrl+Enter sends.
- A–D work as keys while the card has focus.
- The payload is exactly what will run: the full mail, the amount and recipient, the command, the DNS change before and after. Never a summary.
- Show the risk family, the age and the escalation time. Nothing runs automatically when time is up.
- After a decision the card collapses to one line: letter, who, when (and the change or instruction).
- The A–D wording can be changed through `options`.

## Example

```tsx
import * as React from "react";
import { ApprovalCard } from "@twentyfirstmedia/pro";
import type { ApprovalDecision } from "@twentyfirstmedia/pro";

function App() {
  const d = React.useState<ApprovalDecision | null>(null);
  return (
    <div
      className="tf-col"
      style={{
        gap: 16,
      }}
    >
      <ApprovalCard
        agent="Atlas"
        project="Support"
        family="comms"
        age="4 min"
        title="Send this email to ida@northwind.io"
        text="Atlas is set to drafts only, so it needs your OK."
        escalation="Escalates to the owner in 1:52 h"
        trace={<a href="#">Open trace</a>}
        decision={d[0]}
        onDecide={(k, t) => {
          d[1]({
            key: k,
            by: "Mara Ellis",
            at: "09:41",
            text: t,
          });
        }}
      >
        <div className="tf-approval-mail">
          {
            "Hi Ida,\n\nhappy to add 40 seats before your renewal on May 1. The quote is attached.\n\nBest,\nMara"
          }
        </div>
      </ApprovalCard>
      <ApprovalCard
        agent="Ledger"
        project="Finance"
        family="money"
        title="Refund €10.00 to the card ending 4242"
        decision={{
          key: "C",
          by: "Lukas",
          at: "Yesterday 18:02",
        }}
      />
      <ApprovalCard
        agent="Scout"
        project="Domains"
        family="infra"
        title="Add TXT record ops-test"
        decision={{
          key: "B",
          by: "Mara Ellis",
          at: "08:10",
          text: "Use TTL 300 instead",
        }}
      />
    </div>
  );
}
export default App;
```

## Props

### ApprovalCard

| Prop | Type | Notes |
| --- | --- | --- |
| `title` | `ReactNode` | Required |
| `text?` | `ReactNode` |  |
| `children?` | `ReactNode` |  |
| `agent?` | `string` |  |
| `project?` | `string` |  |
| `family?` | `RiskFamily` |  |
| `familyLabel?` | `string` |  |
| `age?` | `string` |  |
| `escalation?` | `ReactNode` |  |
| `trace?` | `ReactNode` |  |
| `note?` | `ReactNode` |  |
| `heading?` | `string` |  |
| `options?` | `ApprovalOption[]` |  |
| `approveLabel?` | `string` |  |
| `onDecide?` | `(key: ApprovalKey, text?: string) => void` |  |
| `decision?` | `ApprovalDecision \| null` |  |
| `compact?` | `boolean` |  |
| `onApprove?` | `() => void` |  |
| `onDeny?` | `() => void` |  |
| `onEdit?` | `(text: string) => void` |  |
| `state?` | `"approved" \| "denied"` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |
