Confirmation dialog for destructive or irreversible actions. It cannot be dismissed by clicking outside, and can ask the user to type a word before the confirm button works. Open it with DialogTrigger.
import { AlertDialog, Button, DialogTrigger } from "@rdloom/react";
export default function AlertDialogBasicExample() {
return (
<DialogTrigger>
<Button variant="secondary">Discard draft</Button>
<AlertDialog
title="Discard this draft?"
description="Your changes have not been saved and will be lost."
confirmLabel="Discard draft"
cancelLabel="Keep editing"
/>
</DialogTrigger>
);
}Installation
npx rdloom add alert-dialogCopies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.
Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/alert-dialog.jsonWorks, but without upgrade tracking.
Usage
import { AlertDialog, Button, DialogTrigger } from "@rdloom/react";
<DialogTrigger>
<Button variant="secondary">Discard draft</Button>
<AlertDialog
title="Discard this draft?"
description="Your changes have not been saved and will be lost."
confirmLabel="Discard draft"
cancelLabel="Keep editing"
/>
</DialogTrigger>Danger delete
import { AlertDialog, Button, DialogTrigger } from "@rdloom/react";
export default function AlertDialogDangerDeleteExample() {
return (
<DialogTrigger>
<Button variant="danger">Delete user</Button>
<AlertDialog
tone="danger"
title="Delete this user?"
description="Asha Menon will lose access immediately. This cannot be undone."
confirmLabel="Delete user"
onConfirm={() => {
// Remove the user here.
}}
/>
</DialogTrigger>
);
}Type to confirm
import { AlertDialog, Button, DialogTrigger } from "@rdloom/react";
export default function AlertDialogTypeToConfirmExample() {
return (
<DialogTrigger>
<Button variant="danger">Delete workspace</Button>
<AlertDialog
tone="danger"
title="Delete the Northwind workspace?"
description="All projects, invoices and members in this workspace are removed for good."
confirmLabel="Delete workspace"
confirmText="Northwind"
onConfirm={() => {
// Remove the workspace here.
}}
/>
</DialogTrigger>
);
}Async confirm
import { AlertDialog, Button, DialogTrigger } from "@rdloom/react";
export default function AlertDialogAsyncConfirmExample() {
return (
<DialogTrigger>
<Button variant="danger">Archive invoices</Button>
<AlertDialog
tone="danger"
title="Archive 24 invoices?"
description="They move out of the active list. The dialog stays open until the request finishes."
confirmLabel="Archive invoices"
onConfirm={() => new Promise<void>((resolve) => setTimeout(resolve, 1500))}
/>
</DialogTrigger>
);
}API Reference
Defined by the spec. Components also accept the props of the React Aria component they wrap.
| Prop | Type | Default |
|---|---|---|
titlerequiredHeading, also the dialog's accessible name. Phrase it as the question being asked, such as Delete this user? | string | none |
descriptionWhat will happen and whether it can be undone. Wired to the dialog with aria-describedby. | string | none |
confirmLabelLabel of the confirm button. Name the action, such as Delete user. | string | "Confirm" |
cancelLabelLabel of the cancel button. | string | "Cancel" |
toneDanger styles the confirm button as destructive and starts focus on the cancel button. | "default" | "danger" | "default" |
confirmTextA word the user must type exactly (such as the resource name) before the confirm button is enabled. | string | none |
onConfirmCalled when the user confirms. If it returns a promise, the confirm button shows a pending state and the dialog cannot be dismissed until it settles. The dialog closes when it resolves and stays open if it rejects. | () => void | Promise<void> | none |
onCancelCalled when the user cancels with the cancel button or Esc. | () => void | none |
isOpenControlled open state, for dialogs opened without a DialogTrigger. | boolean | none |
defaultOpenInitial open state when uncontrolled and used without a DialogTrigger. | boolean | none |
onOpenChangeCalled when the dialog opens or closes (without a DialogTrigger). | (isOpen: boolean) => void | none |
Accessibility
Role alertdialog, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.
Keyboard
- Esc cancels
- Tab and Shift+Tab stay inside the dialog
- Enter in the confirmation field confirms once the word matches
Screen readers announce
- Opening announces an alert dialog with its title, then reads the description
- In the danger tone focus lands on Cancel, so a stray Enter does not destroy anything
- With confirmText, the field is labelled with the word to type, and Confirm is announced as dimmed until it matches
- While pending the confirm button is announced as busy
- Closing announces the button that opened it (focus is back there)
What your code must do
- Focus moves into the dialog on open (to Cancel for the danger tone) and returns to the trigger on close
- Clicking outside does not dismiss it
- The description is linked with aria-describedby so it is read on open
- While a confirm is pending, Esc and Cancel are blocked and the confirm button is marked busy
- Content behind the dialog is hidden from assistive technology
Guidelines
Use it when
- Confirming a destructive or irreversible action such as deleting a record
- Making the user prove intent by typing a resource name
Avoid it when
- Non-destructive choices: use Dialog
- Actions that can be undone: use a toast with Undo
- Forms with several fields: use Dialog or Sheet
Don't
- Vague buttons like OK or Yes: name the action
- Using it for routine confirmations, which trains people to click through
- Closing the dialog yourself before an async onConfirm settles
Design tokens
The semantic tokens this component uses. Change them once and every component follows; see Design tokens.
--rd-color-overlay-backdrop--rd-color-surface-raised--rd-color-border-default--rd-color-text-default--rd-color-text-muted--rd-radius-overlay