Confirm Dialog
A packaged confirmation dialog for destructive and consequential actions. Every close produces exactly one outcome, and a deliberate cancel is reported separately from a dismissal.
Examples
Destructive
confirm 0 · cancel 0 · dismiss 0
Typed confirmation, consequences, and countdown
Overview
ConfirmDialog is opened imperatively through NiceModal: there is no trigger element to wire up, so any handler can raise it. It awaits onConfirm, showing a loading confirm button and refusing to close until the promise settles, and it also resolves the promise returned by show() with a boolean for callers that prefer await.
import { ConfirmDialog } from "@rogo-technologies/ui/confirm-dialog";
import { useModal } from "@rogo-technologies/ui/modal";
const modal = useModal(ConfirmDialog);
await modal.show({
title: "Delete project?",
description: "Members lose access immediately. This can't be undone.",
confirmLabel: "Delete project",
variant: "destructive",
onConfirm: () => deleteProject(id),
});Usage
Awaiting a boolean instead of passing callbacks
show() resolves true on confirm and false on cancel or dismissal, which reads well when the caller already owns the mutation and its error handling.
const confirmed = await modal.show({
title: "Revoke this API client?",
variant: "destructive",
confirmLabel: "Revoke",
});
if (confirmed) revokeClient.mutate(clientId);Cancel vs dismiss
onCancel fires only for the cancel button. Everything else that closes the dialog (the X, Escape, an outside press, a navigation that unmounts it, or a programmatic hide()) fires onDismiss. Use the split when "no" means something different from "not now": tell an analytics event or a "share all chats / only new chats" choice apart from walking away.
await modal.show({
title: "Share existing chats with new members?",
confirmLabel: "Share all chats",
cancelLabel: "Only new chats",
onConfirm: shareAll,
onCancel: shareNewOnly,
onDismiss: () => {},
});Raising the stakes
Three opt-in props make a confirm harder to fire by accident. Reach for them when the action is unrecoverable and wide-reaching, not for routine deletes.
await modal.show({
title: "Delete Acme Capital?",
variant: "destructive",
requireTypedConfirmation: "Acme Capital",
consequences: ["Sign out all 412 members", "Delete 1,204 chats and their citations"],
countdownSeconds: 3,
});ConfirmDialog vs Alert Dialog
| Feature | Alert Dialog | Confirm Dialog |
|---|---|---|
| Composition | Compound primitive you assemble | One packaged component, opened imperatively |
| Trigger | AlertDialogTrigger in the markup | Any handler, via useModal(ConfirmDialog).show() |
| Async confirm | Caller's responsibility | Awaited, with a loading button and close blocked |
| Outcomes | Whatever the caller wires up | Exactly one of confirm / cancel / dismiss |
| Use case | Bespoke confirmation layouts | The standard destructive or consequential decision |
API
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | - | Required. Say what is about to happen. |
description | string | - | Supporting copy under the title. |
confirmLabel | string | "Confirm" | Label of the confirm button. |
cancelLabel | string | "Cancel" | Label of the cancel button. |
variant | ConfirmDialogVariant | "default" | default (no icon), info, warning, or destructive. Also picks the icon and confirm button treatment. |
consequences | string[] | - | Spelled-out effects of confirming. |
requireTypedConfirmation | string | - | Confirm stays disabled until the user types this string exactly. |
countdownSeconds | number | - | Confirm stays disabled for this many seconds after opening. |
onConfirm | () => void | Promise<void> | - | Awaited. The confirm button shows a loading state and the dialog refuses to close. |
onCancel | () => void | - | The cancel button only. |
onDismiss | () => void | - | X, Escape, outside press, navigation, or a programmatic hide. |
ConfirmDialogContent is also exported for hosts that need to supply their own Modal root: apps/frontend uses it so that its router-aware root still closes the dialog on a route change.
Guidelines
Do
- Name the operation in the confirm label ("Delete project", not "OK")
- Pass
variant="destructive"when the action can't be undone - Let
onConfirmbe async and let the dialog own the loading state - Use
requireTypedConfirmationfor tenant-wide or organization-wide deletions
Don't
- Don't confirm routine, reversible actions. Offer an undo instead
- Don't add
countdownSecondsto anything a user does more than rarely; it reads as a punishment - Don't treat a dismissal as a cancel when the two mean different things to your caller
- Don't hand-roll another confirmation modal. Extend this one