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.
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.
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-boxCopies 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.jsonWorks, 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
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.
| Prop | Type | Default |
|---|---|---|
summaryrequiredWhat is about to happen, in plain words, e.g. "Delete 12 draft invoices". | string | none |
riskHow much could go wrong. High risk uses the danger style and interrupts speech. | "low" | "medium" | "high" | "medium" |
reversibleWhether the action can be undone. Unset says nothing about it. | boolean | none |
onApproveCalled when the person approves. | () => void | none |
onDenyCalled when the person declines. | () => void | none |
approveLabelText of the approve button. Say the action: "Delete invoices" beats "OK". | string | "Approve" |
denyLabelText of the decline button. | string | "Deny" |
isPendingTrue while the approval is being processed: shows a spinner and blocks a second press. | boolean | false |
autoFocusMove focus to the box when it appears, so a keyboard user lands on the question. | boolean | false |
childrenExtra detail, e.g. the list of items affected. | node | none |
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