Alert Dialog
A modal dialog for destructive confirmations. The user must explicitly accept or cancel — there is no close button. Built on Radix UI AlertDialog.
Delete account
Revoke access
Installation
pnpm dlx @hex-core/cli add alert-dialogControlled open state
Open programmatically when a form-validation conflict surfaces — trigger lives on the parent rather than inside AlertDialogTrigger
const [conflictOpen, setConflictOpen] = useState(false);
async function onSubmit() {
const conflict = await checkSlugAvailability();
if (conflict) {
setConflictOpen(true);
return;
}
// …save
}
return (
<>
<Button onClick={onSubmit}>Save changes</Button>
<AlertDialog open={conflictOpen} onOpenChange={setConflictOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Slug already in use</AlertDialogTitle>
<AlertDialogDescription>
Another workspace is using this slug. Pick a different one or overwrite the existing record.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Pick another</AlertDialogCancel>
<AlertDialogAction onClick={overwriteRecord}>Overwrite</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
);Pending action
Disable both Cancel and Action while the destructive request is in flight; render a transient label on the action button so the spinner and copy live in the same place
const [pending, setPending] = useState(false);
async function onConfirm(event: React.MouseEvent) {
event.preventDefault();
setPending(true);
try {
await deleteProject(project.id);
} finally {
setPending(false);
}
}
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive">Delete project</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete this project?</AlertDialogTitle>
<AlertDialogDescription>
All issues, comments, and attachments will be permanently removed.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={pending}>Cancel</AlertDialogCancel>
<AlertDialogAction disabled={pending} onClick={onConfirm}>
{pending ? "Deleting…" : "Delete project"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | — | Controlled open state |
defaultOpen | boolean | false | Default open state for uncontrolled usage |
onOpenChange | function | — | Callback fired on open state change: (open: boolean) => void |
AI Guidance
When to use
Use for destructive or irreversible confirmations: delete account, discard changes, permanent actions. The user must explicitly choose Action or Cancel.
When not to use
Don't use for non-destructive dialogs (use Dialog). Don't use for simple notifications (use Toast). Don't use when there's only one action to take.
Common mistakes
- Using Dialog when AlertDialog is semantically required
- Omitting AlertDialogCancel (user must have an escape hatch)
- Putting more than one AlertDialogAction (the pattern expects one destructive action)
- Making the action button non-destructive styled
Accessibility
Radix sets role='alertdialog', traps focus, focuses AlertDialogCancel by default, and closes on Escape. Clicks outside the dialog are prevented (user must choose Cancel or Action).
Token budget: 650
Verified against @hex-core/components@1.12.0