# Alert Dialog (/ui/components/react/alert-dialog)



<Playground component="alert-dialog" />

## Usage [#usage]

```tsx
import {
  AlertDialog,
  AlertDialogTrigger,
  AlertDialogContent,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogDescription,
  AlertDialogBody,
  AlertDialogFooter,
  AlertDialogClose,
} from '@appica/ui-react/alert-dialog'
import { Button } from '@appica/ui-react/button'
```

```tsx
<AlertDialog>
  <AlertDialogTrigger render={<Button variant="outline">Delete project</Button>} />
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>Delete this project?</AlertDialogTitle>
      <AlertDialogDescription>This permanently removes the project and cannot be undone.</AlertDialogDescription>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogClose render={<Button variant="soft">Cancel</Button>} />
      <AlertDialogClose render={<Button variant="destructive">Delete</Button>} />
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>
```

`AlertDialog` is a compound component built on [Base UI's Alert Dialog](https://base-ui.com/react/components/alert-dialog). It's a [`Dialog`](/ui/components/react/dialog) tuned for **confirmations**: it's always modal, it has **no dismiss-by-clicking-outside and no close button**, and it carries `role="alertdialog"` so assistive tech announces it as an interruption. The user has to choose one of the actions you provide. The root holds the open state; the parts compose the trigger and the portalled popup:

* **`AlertDialogTrigger`** — the button that opens the dialog. Use `render` to project it onto your own [`Button`](/ui/components/react/button) (or any element).
* **`AlertDialogContent`** — the portalled, centered popup. It draws the card, the backdrop, and the focus trap, and accepts the shared **modal props** below.
* **`AlertDialogHeader`** — a layout wrapper grouping the title and description with the right spacing.
* **`AlertDialogTitle`** — an `<h2>` that labels the popup (`aria-labelledby`).
* **`AlertDialogDescription`** — a `<p>` that describes it (`aria-describedby`).
* **`AlertDialogBody`** — an optional region for longer content between the header and footer.
* **`AlertDialogFooter`** — a layout wrapper for the actions; stacks on small screens and right-aligns in a row from `sm` up.
* **`AlertDialogClose`** — a button that closes the dialog; project it onto your own [`Button`](/ui/components/react/button) with `render`. Put your confirm **and** cancel actions here.

Reach for an `AlertDialog` only when continuing is **consequential** — deleting data, signing out, discarding edits. For a dismissible panel with arbitrary content, use a [`Dialog`](/ui/components/react/dialog); for a transient, non-blocking message, use a [`Toast`](/ui/components/react/toast).

## Examples [#examples]

### Destructive confirmation [#destructive-confirmation]

The canonical use: a high-stakes action gated behind an explicit confirm. Give the confirm button the `destructive` [`Button`](/ui/components/react/button) variant so the danger reads at a glance, and keep the cancel action first so it's the easy, default-focused choice.

```tsx
import {
  AlertDialog,
  AlertDialogTrigger,
  AlertDialogContent,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogClose,
} from '@appica/ui-react/alert-dialog'
import { Button } from '@appica/ui-react/button'
import { Trash } from '@appica/icons-react'

export default function AlertDialogDestructive() {
  return (
    <AlertDialog>
      <AlertDialogTrigger
        render={
          <Button variant="outline">
            <Trash data-icon="start" />
            Delete account
          </Button>
        }
      />
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Delete your account?</AlertDialogTitle>
          <AlertDialogDescription>
            Your profile, projects, and billing history will be erased immediately. This action cannot be undone.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogClose render={<Button variant="soft">Cancel</Button>} />
          <AlertDialogClose render={<Button variant="destructive">Delete account</Button>} />
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

### With media [#with-media]

Lead the header with media to anchor the message — an [`Avatar`](/ui/components/react/avatar), a [`Thumbnail`](/ui/components/react/thumbnail), or a plain icon. It sits above the title and inherits the dialog's spacing. This example also centers the header (`text-center`) and stretches the actions to equal widths (`flex-1`) for a more focused, app-like layout.

```tsx
import {
  AlertDialog,
  AlertDialogTrigger,
  AlertDialogContent,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogClose,
} from '@appica/ui-react/alert-dialog'
import { Button } from '@appica/ui-react/button'
import { Avatar, AvatarImage, AvatarFallback } from '@appica/ui-react/avatar'
import { Send } from '@appica/icons-react'

export default function AlertDialogMedia() {
  return (
    <AlertDialog>
      <AlertDialogTrigger render={<Button variant="outline">Invite member</Button>} />
      <AlertDialogContent className="text-center">
        <AlertDialogHeader>
          <Avatar size="lg" className="mx-auto mb-1">
            <AvatarImage src="/avatars/01.jpg" alt="Sarah Jenkins" />
            <AvatarFallback>SJ</AvatarFallback>
          </Avatar>
          <AlertDialogTitle>Invite Sarah Jenkins?</AlertDialogTitle>
          <AlertDialogDescription>
            She'll get an email invitation to join the Orbit project as an editor. You can change her role later.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogClose
            render={
              <Button variant="soft" className="flex-1">
                Cancel
              </Button>
            }
          />
          <AlertDialogClose
            render={
              <Button className="flex-1">
                <Send data-icon="start" />
                Send invite
              </Button>
            }
          />
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

### Asynchronous action (controlled) [#asynchronous-action-controlled]

When confirming kicks off async work, keep the dialog open until it settles — this is the everyday reason to control the open state. Drive `open` yourself and **don't** close from `onOpenChange` while the request is in flight: swap the confirm button to a loading state and disable cancel, then close once the promise resolves.

```tsx
'use client'

import * as React from 'react'
import {
  AlertDialog,
  AlertDialogTrigger,
  AlertDialogContent,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogClose,
} from '@appica/ui-react/alert-dialog'
import { Button } from '@appica/ui-react/button'
import { Spinner } from '@appica/ui-react/spinner'

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

  async function confirm() {
    setPending(true)
    await new Promise((resolve) => setTimeout(resolve, 1400))
    setPending(false)
    setOpen(false)
  }

  return (
    <AlertDialog open={open} onOpenChange={(next) => !pending && setOpen(next)}>
      <AlertDialogTrigger render={<Button variant="outline">Reset API key</Button>} />
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Reset your API key?</AlertDialogTitle>
          <AlertDialogDescription>
            The current key stops working at once. Update every service that uses it before continuing.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogClose disabled={pending} render={<Button variant="soft">Cancel</Button>} />
          <Button disabled={pending} onClick={confirm}>
            {pending ? (
              <>
                <Spinner data-icon="start" currentColor className="text-base" />
                Resetting…
              </>
            ) : (
              'Reset key'
            )}
          </Button>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

The header, body, and footer flip to read right-to-left, and the footer's actions mirror to the leading edge. The popup portals to `<body>`, so it follows the **document** direction — when you scope RTL to a subtree of an LTR page, set `dir="rtl"` on `AlertDialogContent` so the popup mirrors too. For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).

<RtlPreview component="alert-dialog" />

## API reference [#api-reference]

`AlertDialog` wraps [Base UI's Alert Dialog](https://base-ui.com/react/components/alert-dialog). Each part forwards every remaining prop to the matching Base UI primitive, so anything it accepts works here — the tables below list the Appica additions and the most common props. See the Base UI docs for the complete API.

### AlertDialog [#alertdialog]

The root. Holds the open state; renders no DOM of its own. Always modal, and not dismissible by an outside click — the user must pick an action.

| Prop                   | <ColMinWidth width="200">Type</ColMinWidth> | Default | <ColMinWidth width="220">Description</ColMinWidth>      |
| ---------------------- | ------------------------------------------- | ------- | ------------------------------------------------------- |
| `open`                 | `boolean`                                   | —       | Controlled open state. Pair with `onOpenChange`.        |
| `defaultOpen`          | `boolean`                                   | `false` | Uncontrolled initial open state.                        |
| `onOpenChange`         | <Code>(open, eventDetails) => void</Code>   | —       | Fires when the dialog opens or closes.                  |
| `onOpenChangeComplete` | `(open) => void`                            | —       | Fires after the open/close transition finishes.         |
| `actionsRef`           | `RefObject`                                 | —       | Imperative handle to `close()` / `unmount()` the popup. |

### AlertDialogTrigger [#alertdialogtrigger]

The button that opens the dialog. Forwards Base UI's `AlertDialog.Trigger`; use `render` to project it onto your own element.

| Prop           | <ColMinWidth width="220">Type</ColMinWidth>                 | Default | Description                                          |
| -------------- | ----------------------------------------------------------- | ------- | ---------------------------------------------------- |
| `render`       | <Code>ReactElement \| (props, state) => ReactElement</Code> | —       | Render the trigger as your own element (a `Button`). |
| `nativeButton` | `boolean`                                                   | `true`  | Whether the rendered element is a native `<button>`. |
| `className`    | `string`                                                    | —       | Extra classes, merged via `tailwind-merge`.          |

### AlertDialogContent [#alertdialogcontent]

The portalled, centered popup. Draws the card, the backdrop, and the focus trap, and maps the underlying Base UI Portal / Backdrop / Viewport so you don't have to reach into them. Shared with [`Dialog`](/ui/components/react/dialog) and [`Drawer`](/ui/components/react/drawer) through the **modal props**.

| Prop            | <ColMinWidth width="170">Type</ColMinWidth> | Default | Description                                                            |
| --------------- | ------------------------------------------- | ------- | ---------------------------------------------------------------------- |
| `backdrop`      | `boolean`                                   | `true`  | Render the dimmed, blurred backdrop behind the popup.                  |
| `initialFocus`  | `RefObject`                                 | —       | Element to focus when the dialog opens (defaults to the first action). |
| `finalFocus`    | `RefObject`                                 | —       | Element to focus when it closes (defaults to the trigger).             |
| `container`     | <Code>HTMLElement \| ref \| null</Code>     | —       | Portal target — render the popup into a different node.                |
| `keepMounted`   | `boolean`                                   | `false` | Keep the popup mounted in the DOM while closed.                        |
| `backdropProps` | `object`                                    | —       | Escape hatch for the backdrop element (`className`, `style`, …).       |
| `viewportProps` | `object`                                    | —       | Escape hatch for the viewport element that centers the popup.          |
| `portalProps`   | `object`                                    | —       | Escape hatch for the portal element.                                   |
| `className`     | `string`                                    | —       | Extra classes on the popup card (e.g. a wider `max-w-*`).              |

### AlertDialogHeader [#alertdialogheader]

A layout wrapper that groups the title and description with consistent padding and spacing.

| Prop        | Type        | Default | Description                                                                      |
| ----------- | ----------- | ------- | -------------------------------------------------------------------------------- |
| `children`  | `ReactNode` | —       | Optional leading media (`Avatar` / `Thumbnail`), the title, and the description. |
| `className` | `string`    | —       | Extra classes, merged via `tailwind-merge`.                                      |

### AlertDialogTitle [#alertdialogtitle]

An `<h2>` that labels the popup. Rendering one sets the popup's `aria-labelledby`.

| Prop        | Type        | Default | Description                                 |
| ----------- | ----------- | ------- | ------------------------------------------- |
| `children`  | `ReactNode` | —       | The title text.                             |
| `className` | `string`    | —       | Extra classes, merged via `tailwind-merge`. |

### AlertDialogDescription [#alertdialogdescription]

A `<p>` that describes the popup. Rendering one sets the popup's `aria-describedby`.

| Prop        | Type        | Default | Description                                 |
| ----------- | ----------- | ------- | ------------------------------------------- |
| `children`  | `ReactNode` | —       | The description text.                       |
| `className` | `string`    | —       | Extra classes, merged via `tailwind-merge`. |

### AlertDialogBody [#alertdialogbody]

An optional region between the header and footer, for confirmations that carry more than a sentence (a changelog, a list of affected items). It doesn't scroll on its own — for content taller than the viewport, wrap it in a [`ScrollArea`](/ui/components/react/scroll-area) between the pinned header and footer.

| Prop        | Type        | Default | Description                                 |
| ----------- | ----------- | ------- | ------------------------------------------- |
| `children`  | `ReactNode` | —       | The body content.                           |
| `className` | `string`    | —       | Extra classes, merged via `tailwind-merge`. |

### AlertDialogFooter [#alertdialogfooter]

A layout wrapper for the actions. Stacks them vertically on small screens (confirm on top) and lays them out in a right-aligned row from `sm` up.

| Prop        | Type        | Default | Description                                 |
| ----------- | ----------- | ------- | ------------------------------------------- |
| `children`  | `ReactNode` | —       | The action buttons.                         |
| `className` | `string`    | —       | Extra classes, merged via `tailwind-merge`. |

### AlertDialogClose [#alertdialogclose]

A button that closes the dialog. Forwards Base UI's `AlertDialog.Close`; project it onto your own element with `render`. Use it for **both** the confirm and the cancel action.

| Prop           | <ColMinWidth width="220">Type</ColMinWidth>                 | Default | Description                                          |
| -------------- | ----------------------------------------------------------- | ------- | ---------------------------------------------------- |
| `render`       | <Code>ReactElement \| (props, state) => ReactElement</Code> | —       | Render the close control as your own element.        |
| `nativeButton` | `boolean`                                                   | `true`  | Whether the rendered element is a native `<button>`. |
| `className`    | `string`                                                    | —       | Extra classes, merged via `tailwind-merge`.          |

## Accessibility [#accessibility]

* The popup has `role="alertdialog"`, with `aria-labelledby` and `aria-describedby` wired automatically from `AlertDialogTitle` and `AlertDialogDescription` — always render both.
* Opening **traps focus** inside the popup and blocks scroll/interaction with the rest of the page; **Escape** closes it and focus returns to the trigger.
* Unlike a [`Dialog`](/ui/components/react/dialog), clicking the backdrop does **not** dismiss it and there's no built-in close button — the user must pick one of your actions, which is the point of an alert dialog.
* Focus lands on the first action by default; point it somewhere safer with `initialFocus` (e.g. the cancel button) for destructive confirmations.
* Popup enter/exit transitions are skipped when the user prefers reduced motion.
