<!-- Protocore Design System — AlertDialog -->
# AlertDialog

- **Category:** Overlay (`overlay`)
- **Slug:** `overlay/alert-dialog`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { AlertDialog, ConfirmDialog } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/overlay/alert-dialog

> A modal that interrupts to confirm a consequential or destructive action — no backdrop escape, an explicit choice required.

## When to use it

Use an AlertDialog when proceeding is **destructive, irreversible, or expensive** — deleting data, revoking access, rolling back a deploy. Unlike a plain **[Dialog](/overlay/dialog)**, it cannot be dismissed by clicking the backdrop; the user must consciously choose Cancel or the action. That friction is the point.

Reach for the `ConfirmDialog` convenience for the common yes/no case, and drop to the raw `AlertDialog.*` parts only when you need custom content between the title and the actions. If the message is purely informational and needs no decision, don't use an AlertDialog at all — a **[Banner](/feedback/banner)** or a toast is enough. And never use it for routine, reversible actions; that trains users to click through the friction.

## Props

### ConfirmDialog

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `cancelLabel` | `string` | no | `Cancel` | Label for the cancel action. |
| `confirmLabel` | `string` | no | `Confirm` | Label for the confirm action. |
| `defaultOpen` | `boolean` | no | — | Uncontrolled initial open state. |
| `description` | `ReactNode` | no | — | Optional supporting copy explaining the consequence. |
| `onConfirm` | `(() => void)` | no | — | Called when the user confirms. May be async; the dialog closes on click. |
| `onOpenChange` | `((open: boolean) => void)` | no | — | Fires when the open state changes. |
| `open` | `boolean` | no | — | Controlled open state. |
| `title` | `ReactNode` | yes | — | Heading of the confirmation. |
| `tone` | `enum` | no | `danger` | Severity of the confirm action — danger renders the destructive button. |
| `trigger` | `ReactNode` | no | — | Optional element that opens the dialog (rendered via `asChild`). |

### AlertDialog.Content

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asChild` | `boolean` | no | — | — |
| `className` | `string` | no | — | — |
| `style` | `CSSProperties` | no | — | — |

## Examples

### Basics

The compound mirrors Dialog but with alert semantics: focus is trapped, there is **no dismiss-on-outside-click**, and the user must pick `Cancel` or `Action`. `Cancel` receives initial focus so the safe choice is the default.

```tsx
import { AlertDialog, Button } from "@protocore/pds";

export default function AlertDialogBasics() {
  return (
    <AlertDialog.Root>
      <AlertDialog.Trigger asChild>
        <Button variant="danger">Revoke key</Button>
      </AlertDialog.Trigger>
      <AlertDialog.Content style={{ width: 440 }}>
        <AlertDialog.Title>Revoke signing key?</AlertDialog.Title>
        <AlertDialog.Description>
          Any service still using <code>sk_live_a19f</code> will start failing within 60 seconds.
          This cannot be undone.
        </AlertDialog.Description>
        <AlertDialog.Footer>
          <AlertDialog.Cancel asChild>
            <Button variant="secondary">Keep it</Button>
          </AlertDialog.Cancel>
          <AlertDialog.Action asChild>
            <Button variant="danger">Revoke now</Button>
          </AlertDialog.Action>
        </AlertDialog.Footer>
      </AlertDialog.Content>
    </AlertDialog.Root>
  );
}
```

### ConfirmDialog convenience

Most confirmations are the same shape, so `ConfirmDialog` packages it: pass `title`, `description`, labels, an `onConfirm` handler, and a `trigger`. It renders tone-styled Cancel / Confirm actions for you.

```tsx
import { useState } from "react";
import { ConfirmDialog, Button, Text } from "@protocore/pds";

export default function ConfirmDialogDemo() {
  const [drained, setDrained] = useState(false);

  return (
    <>
      <ConfirmDialog
        title="Drain node validator-07?"
        description="In-flight requests finish; no new work is scheduled onto it."
        confirmLabel="Drain node"
        cancelLabel="Cancel"
        onConfirm={() => setDrained(true)}
        trigger={<Button variant="secondary">Drain node</Button>}
      />
      <Text size="sm" color="muted" style={{ marginTop: 8 }}>
        {drained ? "validator-07 draining…" : "validator-07 is active"}
      </Text>
    </>
  );
}
```

## Do & don't

**Do**

- Name the consequence in the title ("Revoke key?") and the effect in the description.
- Make the confirm label a verb ("Revoke now"), not a generic "OK".
- Use tone="danger" for anything that destroys or revokes.
- Let Cancel keep initial focus so the safe path is the default.

**Don't**

- Use it for reversible or low-stakes actions — that's a plain Dialog or inline control.
- Re-enable outside-click dismissal — the deliberate choice is the whole point.
- Bury the destructive button as the default focus.
- Show an AlertDialog with no decision to make — use a Banner or toast.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Esc` | Cancels and closes (equivalent to the Cancel action). |
| `Tab` | Moves between Cancel and Action, trapped within the panel. |
| `Shift + Tab` | Moves focus backward, trapped within the panel. |
| `Enter / Space` | Activates the focused Cancel or Action button. |

**Notes**

- Role=alertdialog: assistive tech announces it more assertively than a plain dialog.
- Focus is trapped and Cancel receives it first; focus returns to the trigger on close.
- There is intentionally no dismiss on outside-click — only Cancel, Action, or Esc close it.
- Title labels the dialog and Description describes it, both wired by Radix.

## Related

`dialog`, `sheet`, `banner`, `button`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
