Components · Overlays
ConfirmDialog
Confirmation for consequential actions: danger tone, type-to-confirm, reason, async pending and error.
Type to confirm, with consequences
import { Button, ConfirmDialog } from '@rookcom/monarch-react';
const wait = (ms: number) => new Promise<void>((resolve) => window.setTimeout(resolve, ms));
function DeleteProject() {
return (
<ConfirmDialog
trigger={<Button variant="danger">Delete project</Button>}
tone="danger"
title="Delete this project?"
description="This cannot be undone."
consequences={[
'48 tasks and their comments are deleted',
'6 people lose access',
'Shared links stop working',
]}
confirmPhrase="q3-launch"
confirmLabel="Delete project"
onConfirm={() => wait(1200)}
/>
);
}
<DeleteProject />Usage examples
Required reason
import { Button, ConfirmDialog } from '@rookcom/monarch-react';
const wait = (ms: number) => new Promise<void>((resolve) => window.setTimeout(resolve, ms));
function DeclineRequest() {
return (
<ConfirmDialog
trigger={<Button>Decline request</Button>}
title="Decline this access request?"
description="The requester is told by email."
reason={{
label: 'Reason',
description: 'Shown to the requester.',
required: true,
placeholder: 'For example: use the shared team account instead',
}}
confirmLabel="Decline request"
onConfirm={() => wait(800)}
/>
);
}
<DeclineRequest />Error and retry
import { Button, ConfirmDialog } from '@rookcom/monarch-react';
const wait = (ms: number) => new Promise<void>((resolve) => window.setTimeout(resolve, ms));
function RevokeWithError() {
return (
<ConfirmDialog
trigger={<Button>Revoke key</Button>}
tone="danger"
title="Revoke this API key?"
description="Requests signed with it start failing straight away."
confirmLabel="Revoke key"
onConfirm={async () => {
await wait(800);
throw new Error('The key is in use by a running export. Try again when it finishes.');
}}
/>
);
}
<RevokeWithError />From a menu with useConfirm
import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, useConfirm } from '@rookcom/monarch-react';
// MoreIcon, TrashIcon: demo icons from the docs site; use your own.
const wait = (ms: number) => new Promise<void>((resolve) => window.setTimeout(resolve, ms));
function RowActions() {
const { confirm, element } = useConfirm();
return (
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button trailingIcon={<MoreIcon />}>Note actions</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Pin</DropdownMenuItem>
<DropdownMenuItem
tone="danger"
icon={<TrashIcon />}
onSelect={async () => {
const result = await confirm({
tone: 'danger',
title: 'Delete this note?',
description: 'It is removed for everyone on the account.',
confirmLabel: 'Delete note',
onConfirm: () => wait(600),
});
if (result) {
// The note is deleted; update the list here.
}
}}
>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
{element}
</>
);
}
<RowActions />Purpose
Asks the person to confirm an action before it happens, says plainly what will change, and stays open while the action runs so a failure can be shown and retried. It replaces window.confirm and window.prompt, which cannot be themed, translated or show progress.
Use when
- Deleting, revoking, erasing or anything else that cannot be undone.
- Actions with wide effects (archive a workspace, remove a member) where a reason may be recorded.
- Irreversible actions that deserve a typed confirmation (the record’s name).
Avoid when
- Reversible actions: do them and offer Undo in a toast instead.
- Collecting more than a short reason: use a Dialog with a form.
Design principles
- Say what happens, not "Are you sure?": the title asks the question, the description and consequences say what changes.
- Name the button after the action ("Delete project"), never "OK" or "Yes".
- Safe by default: focus starts on Cancel, the least destructive action; outside clicks do not dismiss it; Enter in a field never confirms a mismatched phrase.
- Honest progress: while the action runs nothing dismisses the dialog, and a failure is shown in place with the reason and a chance to retry.
- The danger tone is reserved for destructive actions: a red confirm button and a calm warning icon, nothing more.
API reference
import { ConfirmDialog, useConfirm } from '@rookcom/monarch-react';| Export | Kind | Rendering | Root class |
|---|---|---|---|
ConfirmDialog | component | client | .mn-confirm-dialog |
useConfirm | hook | client | – |
ConfirmDialog is controlled (open + onOpenChange) or uncontrolled (trigger, defaultOpen). It forwards its ref to the dialog element.
onConfirm(details) runs the action. Return a promise for the pending state; reject (or throw) to show an error and keep the dialog open.
useConfirm() returns { confirm, element }: render element once and await confirm(options), which resolves with { reason? } or null when cancelled.
Other DialogContent props (className, onEscapeKeyDown, data-*, …) pass through to the dialog.
Generated from the TypeScript declarations of packages/react/src/components/overlays/confirm-dialog.
Properties
reason is { label, description?, placeholder?, required?, maxLength?, requiredMessage? }; the trimmed text arrives as details.reason.
pending and error can be controlled when the product tracks the mutation itself.
Every visible string is a prop (confirmLabel, cancelLabel, confirmPhraseLabel, confirmPhraseMismatch, errorMessage, reason.requiredMessage) for translation.
ConfirmDialog
Confirmation for consequential actions, built on Dialog with `role="alertdialog"`. Supports a danger tone, a consequence list, type-to-confirm, an optional or required reason, and async actions with pending and error states. Focus starts on Cancel, the least destructive action, and nothing dismisses the dialog while the action runs.
| Prop | Type | Description |
|---|---|---|
onConfirm required | (details: ConfirmDetails) => unknown | Runs the action. Return a promise to keep the dialog open in a pending state until it settles: on success it closes (see `closeOnConfirm`), on rejection the error is shown inside the dialog and the person can retry. |
title required | ReactNode | The question, e.g. "Delete this project?". Becomes the accessible name. |
asChild | boolean | |
cancelLabel | ReactNode | Default "Cancel". |
children | ReactNode | Extra content between the description and the fields, e.g. a step-up slot. |
className | string | |
closeLabel | string | null | Accessible label of the built-in close button. Pass null to hide the button. |
closeOnConfirm | boolean | Close after `onConfirm` succeeds. Default `true`. |
closeOnOutsideClick | boolean | Whether a click outside the dialog closes it. Default `true`; turn it off for forms with typed values so a stray click does not lose them. Escape and the close button still work. |
confirmLabel | ReactNode | Label of the confirming button. Name the action ("Delete project"), not "OK". Default "Confirm". |
confirmPhrase | string | Type-to-confirm: the confirm button stays disabled until this exact phrase (case-sensitive, surrounding spaces ignored) is typed. |
confirmPhraseLabel | ReactNode | Label of the phrase field. Default: Type "<phrase>" to confirm. |
confirmPhraseMismatch | ReactNode | Shown when the person submits a phrase that does not match. |
consequences | ReactNode[] | Concrete consequences, shown as a list ("12 tasks will be deleted"). |
defaultOpen | boolean | Uncontrolled initial open state. |
description | ReactNode | What will happen. Becomes the accessible description. |
error | ReactNode | Controlled error message, shown above the actions. |
errorMessage | ((error: unknown) => ReactNode) | Turns a rejected `onConfirm` into a message. Default: the Error's message, or a generic line. |
escapeGuard | boolean | Ignore Escape while focus is on a control whose own popup is open (a combobox or menu button with `aria-expanded="true"`), so that press only closes the popup. Default `true`. |
fullScreenOnPhone | boolean | Below 640px wide, fill the whole screen instead of floating with a margin. |
id | string | |
initialFocus | "cancel" | "field" | Where focus goes on open. `cancel` (default) is the least destructive action; `field` focuses the phrase or reason field when there is one. |
onCancel | (() => void) | Called when the dialog is dismissed without confirming. |
onOpenChange | ((open: boolean) => void) | |
open | boolean | Controlled open state. |
pending | boolean | Controlled pending state, for actions the product tracks itself. |
reason | ConfirmReasonField | Ask for a reason (optional or required); it is passed to `onConfirm`. |
size | "sm" | "md" | "lg" | "xl" | Dialog width. Default `sm`. |
tone | "danger" | "default" | `danger` for destructive or irreversible actions: red confirm button and a warning icon. |
trigger | ReactElement<unknown, string | import("/home/runner/work/monarch/monarch/node_modules/@types/react/index").JSXElementConstructor<any>> | The element that opens the dialog (rendered with `asChild`), e.g. a Button. |
Variants
| Prop | Values | Description |
|---|---|---|
tone | 'default''danger' | danger uses the danger Button and a warning icon in the header. Exposed as data-tone. |
confirmPhrase | 'undefined''string' | Type-to-confirm. The confirm button is disabled until the phrase matches exactly (case-sensitive; surrounding spaces ignored). |
reason | 'undefined''optional''required' | A labelled textarea; required keeps the confirm button disabled until it has text. |
size | 'sm''md''lg''xl' | Dialog width; default sm. |
States
| State | Exposed as | Behaviour |
|---|---|---|
| Blocked | disabled (confirm button) | The phrase does not match or a required reason is empty. |
| Mismatch | aria-invalid (field) | Pressing Enter with a wrong phrase, or confirming without a required reason, shows a field error. |
| Pending | aria-busy (dialog and confirm button), data-busy | The confirm button shows a spinner and keeps focus; Cancel is disabled; Escape and outside clicks are ignored; fields are read-only. |
| Error | role="alert" | A danger Alert above the actions, also linked to the confirm button with aria-describedby. The person can retry or cancel. |
Interaction behaviour
- Opening focuses Cancel (
initialFocus="cancel"), or the phrase or reason field withinitialFocus="field". - Confirming calls
onConfirm. A synchronous action closes the dialog at once; a promise keeps it pending until it settles. - On success it closes (unless
closeOnConfirm={false}); on failure it stays open with the error and the confirm button ready to retry. - Escape and Cancel call
onCanceland close it, except while pending. - Every opening starts clean: typed phrase, reason and old errors are cleared.
- From a DropdownMenu item, call
confirm()fromuseConfirminonSelect; focus returns to the menu trigger afterwards.
Keyboard support
| Keys | Action |
|---|---|
| Tab / Shift+Tab | Moves between the fields, Cancel and the confirm button; focus stays inside. |
| Enter (phrase field) | Confirms when the phrase matches; otherwise shows the mismatch error. |
| Enter / Space (button) | Activates Cancel or the confirm button. |
| Escape | Cancels and returns focus to the trigger; ignored while pending. |
Accessibility
- Role
- alertdialog (aria-modal)
- Accessible name
title;descriptionbecomes the accessible description.
- Initial focus is on the least destructive action (APG alert dialog pattern).
- The phrase field is labelled with the exact phrase and has autocomplete, autocorrect and spellcheck off.
- Field errors are announced politely and linked to the field; an action error is announced assertively (
role="alert"). - The pending confirm button keeps focus and sets
aria-busy, so keyboard users do not lose their place. - The warning icon is decorative (
aria-hidden); the title carries the meaning.
Responsive behaviour
- Default width 24rem (
sm), never wider than the screen minus 2rem. - The footer wraps its buttons on narrow screens; long phrases wrap inside the label.
- Long consequence lists scroll inside the body while the actions stay visible.
Theme support
- Dialog surface tokens, danger tokens for the icon and button, and the muted surface behind the phrase.
- Light, dark and more-contrast themes apply automatically; motion follows
prefers-reduced-motion.
Do and don't
Do
- Describe the concrete result, with counts, in
descriptionorconsequences. - Use
confirmPhrasewith the record’s own name for irreversible, high-impact actions. - Return the mutation’s promise from
onConfirmso progress and failure show in the dialog. - Throw errors with a message the person can act on, or map them with
errorMessage.
Don't
- Do not confirm reversible actions; offer Undo instead.
- Do not use "OK" / "Cancel" or a vague "Are you sure?" title.
- Do not use the danger tone for non-destructive actions.
- Do not close the dialog yourself before the action has finished.
Related components
- Dialog: The base it is built on, for custom content.
- DropdownMenu: Destructive menu items that open a confirmation.
- Toast: Undo for reversible actions instead of a confirmation.
- Alert: Shows the error inside the dialog.
Dependencies
Testing coverage
- Opens as an alertdialog with focus on Cancel; axe passes; Escape cancels (onCancel) and restores focus.
- Keeps focus inside and ignores outside clicks.
- Confirms a synchronous action and closes.
- Type-to-confirm: blocked until the exact phrase; Enter with a mismatch marks the field invalid and describes the error; surrounding spaces are ignored.
- Async: pending sets aria-busy, keeps focus on the confirm button, disables Cancel and blocks Escape; success closes.
- Async: a rejection shows the error, links it to the confirm button, keeps it open and allows retry.
- Generic message for non-Error rejections;
errorMessagemaps errors; a synchronous throw is shown too. - Required reason blocks until filled and is passed trimmed; an empty optional reason is omitted.
- Consequences render as a list; children come before the fields.
- Controlled
pendinganderror; controlledopenwithcloseOnConfirm={false}. - Starts clean on every opening.
- useConfirm resolves with the details on confirm and null on cancel, and runs async work inside the dialog first.
- useConfirm from a DropdownMenu item: focus lands on Cancel, and returns to the menu trigger after closing.
- Renders on the server (closed and open).
| Check | Covered |
|---|---|
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: gallery/confirm-dialog and overlays/confirm-dialog-light (open); Cross-browser axe: /components/confirm-dialog |
Tests: packages/react/src/components/overlays/confirm-dialog