Dashral Pro is on the wayGet 30% off at launch

Dashral

Approval Card

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.json
Remind the customers who haven't paid yet.
  1. Find unpaid invoices: Done
    Find unpaid invoices3 invoices past due, $2,520 in all640ms

Needs your approval

Send 3 payment reminders

Each customer gets the friendly first reminder, with their invoice attached.

To
Northwind, Sagara Resorts, Kopi Senja
Template
First reminder
Total due
$2,520

Installation

$ pnpm dlx shadcn@latest add https://dashral.com/r/approval-card.json

Adds 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:

components.json
{
  "registries": {
    "@dashral": "https://dashral.com/r/{name}.json"
  }
}

Then add components by name:

$ pnpm dlx shadcn@latest add @dashral/approval-card

Usage

example.tsx
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.

ApprovedExport October's orders to CSV
Always allowedRead your calendar
DeniedRefund order #5821

API reference

ApprovalCard

Also takes every prop of <section>(opens in a new tab).

PropTypeDefault
titleReactNode

What the assistant wants to do, as an action. Required.

—
descriptionReactNode

Why, or what will happen.

—
details{ label, value }[] | ReactNode

Exactly 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 | Promise

While its promise is pending, the button shows it's working.

—
onDeny() => void | Promise—
allowAlwaysboolean

Offers Always allow beside Allow once.

false
destructiveboolean

For what can't be undone: approving is red, with a warning sign.

false
approveLabel / denyLabelstring

Your words on the buttons.

—
resultReactNode

Said on the decided line, e.g. "Reminders sent".

—
iconReactNode

Replaces the shield (or the warning sign).

—
classNames{ details?, actions? }

Classes for the rows and the buttons' bar.

—
classNamestring

Extra classes, merged with cn() so yours win.

—

Keyboard

KeysAction
TabDeny, Always allow, then Approve
EnterorSpaceChoose

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.