# Alert Dialog

A dialog that stops the user until they answer a question.

```tsx
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
  Button,
  toast,
} from 'ferry-ui'
import { Trash2 } from 'lucide-react'

export default function AlertDialogHero() {
  return (
    <AlertDialog>
      <AlertDialogTrigger asChild>
        <Button variant="destructive" icon={<Trash2 />}>
          Delete project
        </Button>
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Delete this project?</AlertDialogTitle>
          <AlertDialogDescription>
            The project “Billing portal” and all its files are deleted. This cannot be undone.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction variant="destructive-solid" onClick={() => toast.success('Project deleted')}>
            Delete project
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

## Usage guidelines

- **Use Confirm Dialog first.** [Confirm Dialog](/docs/components/confirm-dialog) is the pattern for a confirmation. Use `AlertDialog` only for a custom layout.
- **Two choices only.** The user cancels or confirms. For a form or a task, use [Dialog](/docs/components/dialog).
- **Not for feedback.** To show the result of an action, use [Toast](/docs/components/toast).
- **Ask a question.** Write the title as a question. Write the consequence in the description.

## Anatomy

Import the parts. Put them together in this order.

```tsx title="Anatomy"

  AlertDialog,
  AlertDialogAction,
  AlertDialogBody,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from 'ferry-ui'

<AlertDialog>
  <AlertDialogTrigger />
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle />
      <AlertDialogDescription />
    </AlertDialogHeader>
    <AlertDialogBody />
    <AlertDialogFooter>
      <AlertDialogCancel />
      <AlertDialogAction />
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>
```

| Part | Role |
| --- | --- |
| `AlertDialog` | Holds the open state. |
| `AlertDialogTrigger` | Opens the dialog on a click. |
| `AlertDialogContent` | The panel. It adds the backdrop. It has no close button. |
| `AlertDialogHeader` | Holds the title and the description. |
| `AlertDialogTitle` | The question. It is the accessible name of the dialog. |
| `AlertDialogDescription` | The consequence of the action. |
| `AlertDialogBody` | Optional content between the header and the footer. |
| `AlertDialogFooter` | The two buttons. Cancel comes first, the action comes last. |
| `AlertDialogCancel` | Closes the dialog and does nothing else. |
| `AlertDialogAction` | Runs `onClick`, then closes the dialog. |

With `asChild`, the trigger gives its behavior to its child element. Use it with a [Button](/docs/components/button).

`AlertDialogContent` renders `AlertDialogPortal` and `AlertDialogOverlay` for you. Use these two parts only to build a custom panel.

## Examples

### Variant of the action

`AlertDialogAction` has the `primary` variant by default. Keep it for an action that deletes nothing. For a destructive action, pass `variant="destructive-solid"`.

```tsx
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
  Button,
  toast,
} from 'ferry-ui'
import { Send } from 'lucide-react'

export default function AlertDialogPrimaryAction() {
  return (
    <AlertDialog>
      <AlertDialogTrigger asChild>
        <Button icon={<Send />}>Send invoice</Button>
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Send invoice INV-2041?</AlertDialogTitle>
          <AlertDialogDescription>
            Acme gets the invoice by email. After that, you cannot edit the invoice.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Not yet</AlertDialogCancel>
          {/* No variant: the action keeps the default `primary` look. */}
          <AlertDialogAction onClick={() => toast.success('Invoice sent')}>Send invoice</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

### Button props

`AlertDialogCancel` and `AlertDialogAction` each render a `Button`. Each one accepts `variant` and `size`. Give the two buttons the same size.

```tsx
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
  Button,
} from 'ferry-ui'

export default function AlertDialogButtons() {
  return (
    <AlertDialog>
      <AlertDialogTrigger asChild>
        <Button>Sign out everywhere</Button>
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Sign out of all devices?</AlertDialogTitle>
          <AlertDialogDescription>
            Each other session of your account ends now. You stay signed in on this device.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel variant="ghost" size="md">
            Cancel
          </AlertDialogCancel>
          <AlertDialogAction variant="warning" size="md">
            Sign out everywhere
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

### More content

Put a list or a warning in `AlertDialogBody`. If the content is too tall, the body scrolls.

```tsx
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogBody,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
  Button,
} from 'ferry-ui'

const MEMBERS = [
  { name: 'Maya Chen', email: 'maya@example.com', role: 'Admin' },
  { name: 'Sam Lee', email: 'sam@example.com', role: 'Developer' },
  { name: 'Ada Park', email: 'ada@example.com', role: 'Viewer' },
]

export default function AlertDialogWithBody() {
  return (
    <AlertDialog>
      <AlertDialogTrigger asChild>
        <Button variant="destructive">Remove 3 members</Button>
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Remove 3 members?</AlertDialogTitle>
          <AlertDialogDescription>They lose access to each project of this workspace.</AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogBody className="gap-0 py-2">
          {MEMBERS.map((member) => (
            <div key={member.email} className="flex items-center justify-between gap-3 border-b py-2 last:border-b-0">
              <div className="flex min-w-0 flex-col">
                <span className="truncate text-[13px] font-medium text-foreground">{member.name}</span>
                <span className="truncate text-xs text-foreground-lighter">{member.email}</span>
              </div>
              <span className="text-xs text-foreground-light">{member.role}</span>
            </div>
          ))}
        </AlertDialogBody>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction variant="destructive-solid">Remove members</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

### Open state and async action

A click on `AlertDialogAction` closes the dialog. To keep the dialog open during a request, pass `open` and `onOpenChange`. Then call `event.preventDefault()` in `onClick`.

Set `loading` on the action until the request ends. The button shows a spinner and refuses clicks.

```tsx
import * as React from 'react'
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
  Button,
  toast,
} from 'ferry-ui'

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

  function revoke(event: React.MouseEvent) {
    // Keep the dialog open until the request ends.
    event.preventDefault()
    setPending(true)
    window.setTimeout(() => {
      setPending(false)
      setOpen(false)
      toast.success('API key revoked')
    }, 1200)
  }

  return (
    <AlertDialog
      open={open}
      onOpenChange={(next) => {
        // Escape does not close the dialog while the request runs.
        if (!pending) setOpen(next)
      }}
    >
      <AlertDialogTrigger asChild>
        <Button variant="destructive">Revoke API key</Button>
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Revoke this API key?</AlertDialogTitle>
          <AlertDialogDescription>Requests with the key “Analytics export” fail from now on.</AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel disabled={pending}>Cancel</AlertDialogCancel>
          <AlertDialogAction variant="destructive-solid" loading={pending} onClick={revoke}>
            Revoke key
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

## Accessibility

- The focus moves to `AlertDialogCancel` when the dialog opens. Thus <Kbd>Enter</Kbd> does not confirm the action accidentally.
- <Kbd>Esc</Kbd> cancels. A click outside the panel does not close the dialog.
- Screen readers announce the title and the description. Always include `AlertDialogTitle` and `AlertDialogDescription`.

## API reference

Each part also accepts the props of its Radix UI primitive and the attributes of its element.

### AlertDialog

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` |  |  |
| `open` | `boolean` |  |  |
| `defaultOpen` | `boolean` |  |  |
| `onOpenChange` | `((open: boolean) => void)` |  |  |

### AlertDialogTrigger

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |

### AlertDialogContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |
| `deferPointerDownOutside` | `boolean` |  | When `true`, a `'pointerdown'` event outside of the layered element will wait for the interaction's click event before dispatching, allowing third-party code to stop propagation of later events and cancel dismissal. |
| `onEscapeKeyDown` | `((event: KeyboardEvent) => void)` |  | Event handler called when the escape key is down. Can be prevented. |
| `onFocusOutside` | `((event: FocusOutsideEvent) => void)` |  | Event handler called when the focus moves outside of the `DismissableLayer`. Can be prevented. |
| `onOpenAutoFocus` | `((event: Event) => void)` |  | Event handler called when auto-focusing on open. Can be prevented. |
| `onCloseAutoFocus` | `((event: Event) => void)` |  | Event handler called when auto-focusing on close. Can be prevented. |
| `forceMount` | `true` |  | Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries. |

### AlertDialogHeader

`AlertDialogHeader` has no props of its own. It accepts the attributes of the element it renders.

### AlertDialogTitle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |

### AlertDialogDescription

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |

### AlertDialogBody

`AlertDialogBody` has no props of its own. It accepts the attributes of the element it renders.

### AlertDialogFooter

`AlertDialogFooter` has no props of its own. It accepts the attributes of the element it renders.

### AlertDialogCancel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"link" \| "default" \| "primary" \| "outline" \| "ghost" \| "destructive" \| "destructive-solid" \| "danger" \| "danger-solid" \| "warning" \| "dashed"` | `default` | Look. `default` (outlined neutral, the default), `primary` (solid: the one main action of a view), `outline` (transparent with a border, on tinted surfaces), `ghost` (borderless, dense rows and toolbars), `destructive` (red outline: starts a destructive action), `destructive-solid` (solid red: the confirm button of a destructive dialog only), `warning` (amber outline), `link` (text link look), `dashed` (filter buttons). `danger` and `danger-solid` are deprecated aliases of `destructive` and `destructive-solid`. |
| `size` | `"tiny" \| "sm" \| "md" \| "lg" \| "icon-tiny" \| "icon" \| "icon-md" \| "icon-lg"` | `sm` | Height. `tiny` 26px (dense inline actions), `sm` 30px (default: toolbars, table rows), `md` 34px (next to form fields), `lg` 38px. Square, icon-only sizes of the same heights: `icon-tiny` 26px, `icon` 30px, `icon-md` 34px, `icon-lg` 38px (they need an `aria-label`). |
| `asChild` | `boolean` |  |  |

### AlertDialogAction

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"link" \| "default" \| "primary" \| "outline" \| "ghost" \| "destructive" \| "destructive-solid" \| "danger" \| "danger-solid" \| "warning" \| "dashed"` | `primary` | Look. `default` (outlined neutral, the default), `primary` (solid: the one main action of a view), `outline` (transparent with a border, on tinted surfaces), `ghost` (borderless, dense rows and toolbars), `destructive` (red outline: starts a destructive action), `destructive-solid` (solid red: the confirm button of a destructive dialog only), `warning` (amber outline), `link` (text link look), `dashed` (filter buttons). `danger` and `danger-solid` are deprecated aliases of `destructive` and `destructive-solid`. |
| `loading` | `boolean` | `false` | Shows a spinner before the label, disables the button and sets `aria-busy`, like `Button`'s `loading`. Clicking still closes the dialog unless `onClick` calls `event.preventDefault()`, so pair it with a controlled `open` for async actions. |
| `size` | `"tiny" \| "sm" \| "md" \| "lg" \| "icon-tiny" \| "icon" \| "icon-md" \| "icon-lg"` | `sm` | Height. `tiny` 26px (dense inline actions), `sm` 30px (default: toolbars, table rows), `md` 34px (next to form fields), `lg` 38px. Square, icon-only sizes of the same heights: `icon-tiny` 26px, `icon` 30px, `icon-md` 34px, `icon-lg` 38px (they need an `aria-label`). |
| `asChild` | `boolean` |  |  |

### AlertDialogPortal

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` |  |  |
| `container` | `Element \| DocumentFragment \| null` |  | Specify a container element to portal the content into. |
| `forceMount` | `true` |  | Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries. |

### AlertDialogOverlay

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |
| `forceMount` | `true` |  | Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries. |
