An AI assistant asking before it acts: what it wants to do, exactly what that means, and Approve or Deny.
$ pnpm dlx shadcn@latest add https://dashral.com/r/approval-card.jsonInstallation
$ pnpm dlx shadcn@latest add https://dashral.com/r/approval-card.jsonAdds the component, the components it builds on, its npm packages and the theme tokens it uses. Using Dashral often? Add the registry to components.json once:
{
"registries": {
"@dashral": "https://dashral.com/r/{name}.json"
}
}Then add components by name:
$ pnpm dlx shadcn@latest add @dashral/approval-cardUsage
import { ApprovalCard } from "@/components/dashral/approval-card"
<ApprovalCard
title="Send 3 payment reminders"
description="Emails the customers whose invoices are overdue."
details={[
{ label: "To", value: "3 customers" },
{ label: "Total due", value: "$1,240" },
]}
onApprove={() => api.reminders.send()}
onDeny={() => api.reminders.cancel()}
/>Examples
Destructive, always allow, and decided
Red for what can't be undone, Allow once or Always allow, and the one-line decided states.
Needs your approval
Delete 214 archived customers
Their orders stay, but their names and emails are removed for good. This can't be undone.
- Archived
- Before January 1, 2025
- Customers
- 214
Needs your approval
Read your calendar
To find a time this week for the quarterly review with the team.
API reference
ApprovalCard
Also takes every prop of <section>(opens in a new tab).
| Prop | Type | Default |
|---|---|---|
title | ReactNodeWhat the assistant wants to do, as an action. Required. | — |
description | ReactNodeWhy, or what will happen. | — |
details | { label, value }[] | ReactNodeExactly what it will do, as rows, or your own content. | — |
status | "pending" | "approved" | "always" | "denied"Controlled decision, e.g. from a saved conversation. Uncontrolled, it starts pending. | — |
onApprove | (scope: "once" | "always") => void | PromiseWhile its promise is pending, the button shows it's working. | — |
onDeny | () => void | Promise | — |
allowAlways | booleanOffers Always allow beside Allow once. | false |
destructive | booleanFor what can't be undone: approving is red, with a warning sign. | false |
approveLabel / denyLabel | stringYour words on the buttons. | — |
result | ReactNodeSaid on the decided line, e.g. "Reminders sent". | — |
icon | ReactNodeReplaces the shield (or the warning sign). | — |
classNames | { details?, actions? }Classes for the rows and the buttons' bar. | — |
className | stringExtra classes, merged with cn() so yours win. | — |
Keyboard
| Keys | Action |
|---|---|
| Tab | Deny, Always allow, then Approve |
| EnterorSpace | Choose |
Accessibility
- A section named by its title; the details are a description list, so each value is read with its label.
- The decision is heard as it's made (a status region that's there from the start), and the keyboard stays on the card when the buttons go.
- A working button is busy (aria-busy) and the others wait, so nothing is chosen twice.
Motion
- A working button shows a spinner; nothing else moves.