# Alert Dialog

A dialog that interrupts the user to confirm an irreversible or consequential action.

```tsx
import * as React from "react"
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@zenoxai/ui/components/alert-dialog"
import { Button } from "@zenoxai/ui/components/button"

function AlertDialogDemo() {
  const [open, setOpen] = React.useState(false)

  return (
    <AlertDialog open={open} onOpenChange={setOpen}>
      <AlertDialogTrigger render={<Button variant="destructive" />}>
        Delete rule
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Delete detection rule?</AlertDialogTitle>
          <AlertDialogDescription>
            &ldquo;Impossible travel&rdquo; stops raising alerts in every
            tenant. Alerts it already raised stay in the queue.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction
            variant="destructive"
            onClick={() => setOpen(false)}
          >
            Delete rule
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

## Usage

```tsx
import * as React from "react"

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@zenoxai/ui/components/alert-dialog"
import { Button } from "@zenoxai/ui/components/button"

function DeleteRuleButton() {
  const [open, setOpen] = React.useState(false)

  return (
    <AlertDialog open={open} onOpenChange={setOpen}>
      <AlertDialogTrigger render={<Button variant="destructive" />}>
        Delete rule
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Delete detection rule?</AlertDialogTitle>
          <AlertDialogDescription>
            It stops raising alerts in every tenant.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction
            variant="destructive"
            onClick={async () => {
              await deleteRule()
              setOpen(false)
            }}
          >
            Delete rule
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

Use an alert dialog before an action the user cannot undo or would not expect: deleting a rule,
revoking a key, archiving alerts in bulk. It asks one question and offers two answers. Unlike
[Dialog](/components/dialog), a click outside does not close it: the user has to choose.

- For a task with fields (rename, create, edit), use [Dialog](/components/dialog).
- For an action that is easy to undo, skip the confirmation: run it and offer "Undo" in a
  [toast](/components/sonner).
- To ask for confirmation from an event handler with one call (`await confirm({...})`), use the
  [Confirm Dialog](/patterns/confirm-dialog) pattern, which is built on this component.

`AlertDialogCancel` closes the dialog. `AlertDialogAction` is a plain `Button`: it runs your
`onClick` and does not close the dialog, so control `open` and close it when the action is done.
Write the title as the question ("Delete detection rule?") and repeat the verb on the action
button ("Delete rule"), never "OK" or "Yes".

## Examples

### With an icon

`AlertDialogMedia` holds an icon above the title, or beside it from the `sm` breakpoint up.

```tsx
import * as React from "react"
import { KeyRoundIcon } from "lucide-react"
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogMedia, AlertDialogTitle, AlertDialogTrigger } from "@zenoxai/ui/components/alert-dialog"
import { Button } from "@zenoxai/ui/components/button"

function AlertDialogWithMedia() {
  const [open, setOpen] = React.useState(false)

  return (
    <AlertDialog open={open} onOpenChange={setOpen}>
      <AlertDialogTrigger render={<Button variant="outline" />}>
        Revoke API key
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogMedia>
            <KeyRoundIcon />
          </AlertDialogMedia>
          <AlertDialogTitle>Revoke API key?</AlertDialogTitle>
          <AlertDialogDescription>
            Integrations that use &ldquo;siem-export&rdquo; stop sending events
            immediately. You cannot restore a revoked key.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Keep key</AlertDialogCancel>
          <AlertDialogAction
            variant="destructive"
            onClick={() => setOpen(false)}
          >
            Revoke key
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

### Small

`size="sm"` narrows the dialog, centers the text and splits the footer into two equal buttons.
Use it for one-line questions.

```tsx
import * as React from "react"
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@zenoxai/ui/components/alert-dialog"
import { Button } from "@zenoxai/ui/components/button"

function AlertDialogSmall() {
  const [open, setOpen] = React.useState(false)

  return (
    <AlertDialog open={open} onOpenChange={setOpen}>
      <AlertDialogTrigger render={<Button variant="outline" />}>
        Sign out everywhere
      </AlertDialogTrigger>
      <AlertDialogContent size="sm">
        <AlertDialogHeader>
          <AlertDialogTitle>Sign out of all sessions?</AlertDialogTitle>
          <AlertDialogDescription>
            You will need to sign in again on every device.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction onClick={() => setOpen(false)}>
            Sign out
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

### Pending action

When the action calls the server, keep the dialog open until the request finishes. Disable both
buttons, show a `Spinner` on the action, and ignore close requests in `onOpenChange` while it
runs.

```tsx
import * as React from "react"
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@zenoxai/ui/components/alert-dialog"
import { Button } from "@zenoxai/ui/components/button"
import { Spinner } from "@zenoxai/ui/components/spinner"

function AlertDialogPending() {
  const [open, setOpen] = React.useState(false)
  const [pending, setPending] = React.useState(false)

  function archive() {
    setPending(true)
    // Stands in for the request that archives the alerts.
    setTimeout(() => {
      setPending(false)
      setOpen(false)
    }, 1200)
  }

  return (
    <AlertDialog
      open={open}
      onOpenChange={(next) => {
        if (!pending) setOpen(next)
      }}
    >
      <AlertDialogTrigger render={<Button variant="outline" />}>
        Archive 12 alerts
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Archive 12 alerts?</AlertDialogTitle>
          <AlertDialogDescription>
            Archived alerts leave the queue and stop counting toward the SLA.
            You can restore them from the archive for 90 days.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel disabled={pending}>Cancel</AlertDialogCancel>
          <AlertDialogAction onClick={archive} disabled={pending}>
            {pending && <Spinner data-icon="inline-start" />}
            {pending ? "Archiving" : "Archive alerts"}
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

## Accessibility

- The popup has `role="alertdialog"`. Focus moves into it on open, stays inside and returns to
  the trigger on close. Escape closes it; a click on the backdrop does not.
- `AlertDialogTitle` names the dialog and `AlertDialogDescription` describes it. Render both:
  the description is where the consequence is spelled out.

## API reference

`import { ... } from "@zenoxai/ui/components/alert-dialog"`

### AlertDialog

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| actionsRef | `RefObject<DialogRootActions \| null>` |  | A ref to imperative actions. - `unmount`: Ends the closing phase of the alert dialog after an externally controlled closing animation finishes. Call `preventUnmountOnClose()` in `onOpenChange` first, otherwise the alert dialog completes closing on its own. Whether it leaves the DOM is decided by `keepMounted` on the portal. - `close`: Closes the alert dialog imperatively when called. |
| defaultOpen | `boolean` | `false` | Whether the dialog is initially open.  To render a controlled dialog, use the `open` prop instead. |
| defaultTriggerId | `string \| null` |  | ID of the trigger that the dialog is associated with. This is useful in conjunction with the `defaultOpen` prop to create an initially open dialog. |
| handle | `AlertDialogHandle<unknown>` |  | A handle to associate the alert dialog with a trigger. If specified, allows external triggers to control the alert dialog's open state. Can be created with the AlertDialog.createHandle() method. |
| onOpenChange | `((open: boolean, eventDetails: AlertDialogRootChangeEventDetails) => void)` |  | Event handler called when the alert dialog is opened or closed. |
| onOpenChangeComplete | `((open: boolean) => void)` |  | Event handler called after any animations complete when the dialog is opened or closed. |
| open | `boolean` |  | Whether the dialog is currently open. |
| triggerId | `string \| null` |  | ID of the trigger that the dialog is associated with. This is useful in conjunction with the `open` prop to create a controlled dialog. There's no need to specify this prop when the dialog is uncontrolled (that is, when the `open` prop is not set). |

### AlertDialogAction

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| focusableWhenDisabled | `boolean` | `false` | Whether the button should be focusable when disabled. |
| nativeButton | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, ButtonState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |
| size | `"default" \| "xs" \| "sm" \| "lg" \| "icon" \| "icon-xs" \| "icon-sm" \| "icon-lg" \| null` |  | Height: `xs` 24px, `sm` 28px, `default` 32px, `lg` 36px. The `icon-*` sizes are square buttons of the same heights for a single icon. The icon follows the size name: 12px at `xs`, 14px at `sm`, 16px at `default` and `lg`. |
| variant | `"link" \| "default" \| "destructive" \| "outline" \| "secondary" \| "ghost" \| null` |  | Visual weight. `default` is the filled primary action, one per view; `outline` and `secondary` are for the other actions; `ghost` has no fill until hover, for toolbars and icon buttons; `destructive` is for delete and other irreversible actions; `link` looks like inline text. |

### AlertDialogCancel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| nativeButton | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DialogCloseState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |
| size | `"default" \| "xs" \| "sm" \| "lg" \| "icon" \| "icon-xs" \| "icon-sm" \| "icon-lg" \| null` | `default` | Height: `xs` 24px, `sm` 28px, `default` 32px, `lg` 36px. The `icon-*` sizes are square buttons of the same heights for a single icon. The icon follows the size name: 12px at `xs`, 14px at `sm`, 16px at `default` and `lg`. |
| variant | `"link" \| "default" \| "destructive" \| "outline" \| "secondary" \| "ghost" \| null` | `outline` | Visual weight. `default` is the filled primary action, one per view; `outline` and `secondary` are for the other actions; `ghost` has no fill until hover, for toolbars and icon buttons; `destructive` is for delete and other irreversible actions; `link` looks like inline text. |

### AlertDialogContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| finalFocus | `boolean \| RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => boolean \| void \| HTMLElement \| null)` |  | Determines the element to focus when the dialog is closed.  - `false`: Do not move focus. - `true`: Move focus based on the default behavior (trigger or previously focused element). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).   Return an element to focus, `true` to use the default behavior, `null` to fall back to the default behavior, or `false`/`undefined` to do nothing. |
| initialFocus | `boolean \| RefObject<HTMLElement \| null> \| ((openType: InteractionType) => boolean \| void \| HTMLElement \| null)` |  | Determines the element to focus when the dialog is opened. By default, focus moves to the first tabbable element inside the popup, except when the dialog is opened by touch — then the popup itself is focused to avoid opening the virtual keyboard.  - `false`: Do not move focus. - `true`: Move focus based on the default behavior (first tabbable element or popup). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).   Return an element to focus, `true` to use the default behavior, `null` to fall back to the default behavior, or `false`/`undefined` to do nothing. |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DialogPopupState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |
| size | `"default" \| "sm"` | `default` | `default` is 400px wide from the sm breakpoint, as wide as a `sm` Dialog, with the header aligned left. `sm` stays 320px wide, centers the header and lays the footer buttons out in two equal columns. |

### AlertDialogDescription

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DialogDescriptionState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

### AlertDialogFooter

No props of its own.

### AlertDialogHeader

No props of its own.

### AlertDialogMedia

No props of its own.

### AlertDialogOverlay

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| forceRender | `boolean` | `false` | Whether the backdrop is forced to render even when nested. |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DialogBackdropState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

### AlertDialogPortal

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| container | `HTMLElement \| ShadowRoot \| RefObject<HTMLElement \| ShadowRoot \| null> \| null` |  | A parent element to render the portal element into. |
| keepMounted | `boolean` | `false` | Whether to keep the portal mounted in the DOM while the popup is hidden. |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DialogPortalState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

### AlertDialogTitle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DialogTitleState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

### AlertDialogTrigger

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| handle | `AlertDialogHandle<unknown>` |  | A handle to associate the trigger with an alert dialog. Can be created with the AlertDialog.createHandle() method. |
| id | `string` |  | ID of the trigger. In addition to being forwarded to the rendered element, it is also used to specify the active trigger for the dialog in controlled mode (with the DialogRoot `triggerId` prop). |
| nativeButton | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| payload | `unknown` |  | A payload to pass to the dialog when it is opened. |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DialogTriggerState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

