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
pnpm dlx @hex-core/cli add alert-dialog

Controlled open state

Open programmatically when a form-validation conflict surfaces — trigger lives on the parent rather than inside AlertDialogTrigger

tsx
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

tsx
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

PropTypeDefaultDescription
open
booleanControlled open state
defaultOpen
booleanfalseDefault open state for uncontrolled usage
onOpenChange
functionCallback 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).

Related components

Token budget: 650