Skip to content

Approval Box

Asks someone to approve or decline something the assistant wants to do. It says what will happen, how risky it is and whether it can be undone, in words as well as color.

AIv0.1.0experimentalWCAG 2.2 AAView spec

Bring your own model.

These components only show what you give them, in one message shape, and report what the person does. They never call a model or a server, so they work with any backend. They are built for how assistive technology handles streaming text, tool steps and approvals.

Send this report to finance@example.com

Medium risk · Can't be undone

Q3 revenue summary, 2 pages.
import { useState } from "react";
import { ApprovalBox } from "@rdloom/react";

export default function ApprovalBoxExample() {
  const [answer, setAnswer] = useState<string | null>(null);
  return (
    <div className="flex w-[30rem] max-w-full flex-col gap-3">
      {answer ? (
        <p className="text-sm">You chose: {answer}</p>
      ) : (
        <ApprovalBox
          summary="Send this report to finance@example.com"
          risk="medium"
          reversible={false}
          approveLabel="Send report"
          onApprove={() => setAnswer("send")}
          onDeny={() => setAnswer("don't send")}
        >
          Q3 revenue summary, 2 pages.
        </ApprovalBox>
      )}
    </div>
  );
}

Installation

npx rdloom add approval-box

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/approval-box.json

Works, but without upgrade tracking.

Usage

import { ApprovalBox } from "@rdloom/react";

<div className="flex w-[30rem] max-w-full flex-col gap-3">
  {answer ? (
    <p className="text-sm">You chose: {answer}</p>
  ) : (
    <ApprovalBox
      summary="Send this report to finance@example.com"
      risk="medium"
      reversible={false}
      approveLabel="Send report"
      onApprove={() => setAnswer("send")}
      onDeny={() => setAnswer("don't send")}
    >
      Q3 revenue summary, 2 pages.
    </ApprovalBox>
  )}
</div>

High risk

Delete 12 draft invoices

High risk · Can't be undone

  • INV-1041 to INV-1052
  • Created between March 3 and March 19
import { ApprovalBox } from "@rdloom/react";

// High risk uses the danger style, says so in words, and is announced assertively.
export default function ApprovalBoxHighRiskExample() {
  return (
    <div className="w-[30rem] max-w-full">
      <ApprovalBox summary="Delete 12 draft invoices" risk="high" reversible={false} approveLabel="Delete invoices" denyLabel="Keep them">
        <ul className="list-disc ps-5">
          <li>INV-1041 to INV-1052</li>
          <li>Created between March 3 and March 19</li>
        </ul>
      </ApprovalBox>
    </div>
  );
}

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
summaryrequired

What is about to happen, in plain words, e.g. "Delete 12 draft invoices".

stringnone
risk

How much could go wrong. High risk uses the danger style and interrupts speech.

"low" | "medium" | "high""medium"
reversible

Whether the action can be undone. Unset says nothing about it.

booleannone
onApprove

Called when the person approves.

() => voidnone
onDeny

Called when the person declines.

() => voidnone
approveLabel

Text of the approve button. Say the action: "Delete invoices" beats "OK".

string"Approve"
denyLabel

Text of the decline button.

string"Deny"
isPending

True while the approval is being processed: shows a spinner and blocks a second press.

booleanfalse
autoFocus

Move focus to the box when it appears, so a keyboard user lands on the question.

booleanfalse
children

Extra detail, e.g. the list of items affected.

nodenone

Accessibility

Role group, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

  • Tab: moves between Deny and Approve
  • Enter / Space: presses the focused button

Screen readers announce

  • On appearing: "Approval needed: Delete 12 draft invoices. High risk. This can't be undone."
  • Then a group named by the summary, with the buttons Deny and Approve

What your code must do

  • A named group: its name is the summary, so the question is read first
  • The box is announced when it appears (assertively for high risk, politely otherwise)
  • Focus goes to the box, not to a button, so a stray Enter can't approve something
  • Risk is shown as words ("High risk") and a different icon, not only by color
  • Deny comes first in the tab order; the approving button names the action
  • The caller is responsible for what happens to focus after an answer (ToolCall returns it to the tool)

Guidelines

Use it when

  • The assistant needs permission before something that changes data, spends money or contacts someone

Avoid it when

  • Asking about something trivial and reversible: just do it and offer undo
  • Collecting several fields: use a Dialog with a form

Don't

  • Approval buttons labelled just OK and Cancel
  • Taking focus onto the approve button
  • Showing risk only with a red color

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-surface-subtle
  • --rd-color-border-strong
  • --rd-color-feedback-danger
  • --rd-color-feedback-danger-subtle
  • --rd-color-feedback-info
  • --rd-color-text-default
  • --rd-color-text-muted
  • --rd-color-focus-ring
  • --rd-radius-overlay