Confirmations and destructive actions
Confirmation dialogs interrupt. Use them only when an action cannot be undone; for everything else, do it immediately and offer Undo.
| Action | Pattern |
|---|---|
| Reversible (archive, hide, move, mark as read) | Do it now, show a toast with Undo |
| Irreversible, low stakes (delete a draft, remove a filter) | AlertDialog with a clear verb |
| Irreversible, high stakes (delete a project, revoke all keys, transfer ownership) | AlertDialog with a typed confirmation |
Undo instead of confirm
toast({
id: "archive",
title: "Project archived",
description: "It is hidden from the dashboard.",
duration: 8000,
action: { label: "Undo", onClick: restore },
});Give the toast a stable id so repeated clicks replace it rather than stack, and a longer duration so there is time to react.
Typed confirmation
<AlertDialog.Root onOpenChange={(open) => !open && setTyped("")}>
<AlertDialog.Trigger asChild>
<Button variant="danger">Delete project</Button>
</AlertDialog.Trigger>
<AlertDialog.Content>
<AlertDialog.Title>Delete “northwind”?</AlertDialog.Title>
<AlertDialog.Description>
This removes 24 deployments, 3 domains and all environment variables. It cannot be undone.
</AlertDialog.Description>
<Field label="Type northwind to confirm">
<Input value={typed} onChange={(e) => setTyped(e.target.value)} autoComplete="off" />
</Field>
<AlertDialog.Footer>
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
<AlertDialog.Action tone="danger" disabled={typed !== "northwind"} onClick={remove}>
Delete project
</AlertDialog.Action>
</AlertDialog.Footer>
</AlertDialog.Content>
</AlertDialog.Root>Writing the dialog
- Title is a question with the object: "Delete “northwind”?" — not "Are you sure?".
- Description states the consequence in concrete numbers, and says it cannot be undone.
- The confirm button repeats the verb: "Delete project", never "OK" or "Yes".
- Cancel is the safe default.
AlertDialogfocuses Cancel when it opens, so pressing Enter by reflex does nothing destructive. Clicking the backdrop does not dismiss it; Escape acts as Cancel. - Use
tone="danger"on the action only when it destroys something.
Where the button lives
Keep destructive actions away from the primary path. On a settings page, put them in a separate section at the bottom ("Danger zone"), with a danger button that opens the dialog — never a primary button that deletes directly.