Confirm Dialog
A promise-based confirmation dialog driven by an imperative confirm() call.
Nothing yet.
Installation
Usage
window.confirm has the right API and the wrong everything else. This is the same call — one line,
inline with the code it guards — rendered as a real dialog.
import { useConfirm } from "@/components/ui/confirm-dialog";
function DeleteButton({ project }: { project: Project }) {
const confirm = useConfirm();
async function handleDelete() {
const ok = await confirm({
title: "Delete project?",
description: `${project.name} and all of its data will be removed.`,
confirmLabel: "Delete",
destructive: true,
});
if (!ok) return;
await deleteProject(project.id);
}
return <Button variant="destructive" onClick={handleDelete}>Delete</Button>;
}No open state, no pending-action state, no dialog markup at the call site — the branch reads the
way the decision actually works.
Setup
Mount the provider once, near the root:
import { ConfirmDialogProvider } from "@/components/ui/confirm-dialog";
export function App({ children }: { children: React.ReactNode }) {
return <ConfirmDialogProvider>{children}</ConfirmDialogProvider>;
}One dialog instance serves the whole tree. Cancelling — button, Escape, or the backdrop — resolves
false; nothing rejects, so there is no try/catch around a user saying no.
Running the action inside the dialog
Pass onConfirm and the dialog stays open while it settles: the confirm button shows a spinner and
both buttons lock. If it throws, the message renders in the dialog and the user can retry — closing
on failure would leave them guessing whether the delete happened.
await confirm({
title: "Revoke API key?",
destructive: true,
onConfirm: () => revokeKey(key.id),
});Without onConfirm the dialog closes immediately and you run the work yourself.
Type-to-confirm
For actions that are genuinely unrecoverable, require the name:
await confirm({
title: "Delete organization",
description: "This cannot be undone.",
confirmText: organization.slug,
confirmLabel: "Delete organization",
destructive: true,
});The confirm button stays disabled until the text matches exactly.
Options
| Option | Description |
|---|---|
title | Required. The question being asked. |
description | Supporting detail. Accepts nodes, not just strings. |
confirmLabel / cancelLabel | Override the provider defaults per call. |
destructive | Style the confirm action as destructive. |
confirmText | Require this exact string to be typed first. |
onConfirm | Run the action inside the dialog, with pending and error states. |
Behavior
- Calling
confirmwhile a dialog is open resolves the previous one as cancelled, so a stray second call cannot leave a promise hanging forever. - While
onConfirmis pending,Escapeand the backdrop do not close the dialog. - The request stays mounted through the close animation and is cleared afterwards, so the text does not vanish mid-fade.
- Writing the label as the verb — "Delete", not "OK" — is what makes the dialog readable when it is the only thing on screen.