# Alert Dialog

> A window over the page that asks people to confirm or cancel something serious, like a delete, before anything happens.

- Name: `alert-dialog`
- Status: beta
- Categories: overlays
- File: the install writes `alert-dialog.tsx` to your `ui` alias, `@/components/ui` by default.
- npm packages: `@base-ui/react`, `class-variance-authority`. The install adds them.
- Other registry items: `@arbokui/action-keys`. The install adds them.
- Gallery: https://arbokui.com/components/alert-dialog
- Registry item: https://arbokui.com/r/alert-dialog.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/alert-dialog
   ```

## Use it for

- Confirming an action that cannot be undone, like deleting a project or leaving with unsaved changes.
- Ask a question in AlertDialogTitle, and say what will happen in AlertDialogDescription.
- Put Cancel first in AlertDialogFooter. It gets focus when the dialog opens, so Enter never confirms by accident.
- Keep the footer to one row: quiet actions lead, and the confirm trails. Use short labels, so two or three buttons fit a 320px screen.
- Name the main button with the action, like 'Delete project', not 'OK' or 'Yes'. Give it tone='danger' when it destroys data.
- For an action that takes time, control open, keep the dialog open while it runs, and close it when it is done.
- The alert dialog opens at the end of the page body. In a box with its own theme, like a dark preview on a light page, pass that box as container on AlertDialogPopup so the alert dialog and its backdrop keep the box's colors.

## Avoid it for

- A form or a task with several fields. Use a dialog.
- A message that needs no answer. Use Alert on the page, or Toast.
- Actions people can undo. Do it at once, and offer Undo for 8 seconds with toast.undoable().
- Opening one when the page loads. People did not ask for it.

## Props

- `AlertDialog`: `{ open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean, details) => void }`. Root. Holds the open state and renders no element. Takes open, defaultOpen, and onOpenChange.
- `open`: `boolean`. AlertDialog. Whether it is open, controlled. Use it with onOpenChange.
- `defaultOpen`: `boolean`. Default `false`. AlertDialog. Whether it is open at the start, when you do not control it.
- `onOpenChange`: `(open: boolean, details) => void`. AlertDialog. Called with the new open state when it opens or closes.
- `AlertDialogTrigger`: `ComponentProps<"button"> & { render?: ReactElement }`. Part. The button that opens it. Pass render={\<Button />} to use your own button. It keeps data-slot="alert-dialog-trigger", even from a server component.
- `AlertDialogPopup`: `ComponentProps<"div">`. Part. The alert dialog, centered over a backdrop. It renders in a portal while open. It has no Close button in the corner.
- `size`: `"sm" | "md" | "lg"`. Default `"md"`. AlertDialogPopup. Most width: 24rem, 28rem, or 32rem. It never gets wider than the screen.
- `AlertDialogTitle`: `ComponentProps<"h2">`. Part. The question it asks, like 'Delete this project?'. An \<h2> by default.
- `AlertDialogDescription`: `ComponentProps<"p">`. Part. What will happen. Screen readers read it when it opens.
- `AlertDialogFooter`: `Omit<ComponentProps<"div">, "color">`. Part. One row of actions at the bottom, lined up at the end. It never wraps, so the confirm always sits last.
- `AlertDialogClose`: `ComponentProps<"button"> & { render?: ReactElement }`. Part. A button that closes it, like Cancel, or the main action when it finishes at once.
- `initialFocus`: `RefObject<HTMLElement | null> | boolean`. AlertDialogPopup. Where focus goes when it opens. By default, the first button, which should be Cancel.
- `container`: `HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null`. AlertDialogPopup. The element the alert dialog and its backdrop open in. By default, the end of the page body, so no parent can clip them. Pass an element, or a ref itself (not ref.current), to open them inside, for example to keep a parent's theme. Only client code has elements and refs. A null container shows no alert dialog, but still locks page scroll until it closes.
- `effect`: `"rise" | "grow"`. Default `"rise"`. AlertDialogPopup. How the alert dialog and its backdrop open and close. rise fades in and rises a little. grow fades in and grows in place, and closes the same way. Left out, it follows an effect-\<name> class on \<body>, where popups open, and rises with none.
- `className`: `string`. AlertDialogPopup. Extra classes. Later classes win, so className='max-w-xl' replaces the width.

## Accessibility

- The popup has role="alertdialog". AlertDialogTitle names it, and AlertDialogDescription describes it, so screen readers read both when it opens.
- While it is open, the rest of the page is hidden from screen readers with aria-hidden and cannot be scrolled or reached.
- Opening it moves focus to the first button. Put Cancel first, so the safe choice has focus.
- Tab stays inside until it closes, even when pressed fast. Then focus goes back to the trigger.
- Escape closes it, as Cancel does. A click on the backdrop does not, so people answer on purpose.
- There is no Close button in the corner. Keep a Cancel button, since touch screen reader users cannot press Escape.
- The theme tests hold dialog text and muted text to 4.5:1 on the popover color, in light and dark.

## Examples

### Confirm a delete (needs @arbokui/button)

```tsx
"use client";

import { Button } from "@/components/ui/button";

function DeleteProject({ onDelete }: { onDelete: () => void }) {
  return (
    <AlertDialog>
      <AlertDialogTrigger render={<Button tone="danger" emphasis="soft" />}>Delete project</AlertDialogTrigger>
      <AlertDialogPopup>
        <AlertDialogTitle>Delete this project?</AlertDialogTitle>
        <AlertDialogDescription>This deletes 24 files and 3 deploys. You cannot undo it.</AlertDialogDescription>
        <AlertDialogFooter>
          <AlertDialogClose render={<Button emphasis="outline" />}>Cancel</AlertDialogClose>
          <AlertDialogClose render={<Button tone="danger" />} onClick={onDelete}>
            Delete project
          </AlertDialogClose>
        </AlertDialogFooter>
      </AlertDialogPopup>
    </AlertDialog>
  );
}
```

### Wait for the action, then close (needs @arbokui/button)

```tsx
"use client";

import { useState } from "react";

import { Button } from "@/components/ui/button";

function RemoveMember({ name, remove }: { name: string; remove: () => Promise<void> }) {
  const [open, setOpen] = useState(false);
  const [loading, setLoading] = useState(false);
  return (
    <AlertDialog open={open} onOpenChange={(next) => !loading && setOpen(next)}>
      <AlertDialogTrigger render={<Button emphasis="outline" />}>Remove {name}</AlertDialogTrigger>
      <AlertDialogPopup size="sm">
        <AlertDialogTitle>Remove {name} from the team?</AlertDialogTitle>
        <AlertDialogDescription>They lose access to every project at once.</AlertDialogDescription>
        <AlertDialogFooter>
          <AlertDialogClose render={<Button emphasis="outline" disabled={loading} />}>Cancel</AlertDialogClose>
          <Button
            tone="danger"
            loading={loading}
            onClick={async () => {
              setLoading(true);
              await remove();
              setLoading(false);
              setOpen(false);
            }}
          >
            Remove
          </Button>
        </AlertDialogFooter>
      </AlertDialogPopup>
    </AlertDialog>
  );
}
```

### Leave without saving (needs @arbokui/button)

```tsx
import { Button } from "@/components/ui/button";

<AlertDialog>
  <AlertDialogTrigger render={<Button emphasis="ghost" />}>Back to projects</AlertDialogTrigger>
  <AlertDialogPopup>
    <AlertDialogTitle>Leave without saving?</AlertDialogTitle>
    <AlertDialogDescription>Your changes to this page will be lost.</AlertDialogDescription>
    <AlertDialogFooter>
      <AlertDialogClose render={<Button emphasis="outline" />}>Keep editing</AlertDialogClose>
      <AlertDialogClose render={<Button tone="danger" />}>Leave</AlertDialogClose>
    </AlertDialogFooter>
  </AlertDialogPopup>
</AlertDialog>
```

### Grows in place (needs @arbokui/button)

```tsx
import { Button } from "@/components/ui/button";

<AlertDialog>
  <AlertDialogTrigger render={<Button emphasis="outline" />}>Discard draft</AlertDialogTrigger>
  <AlertDialogPopup effect="grow">
    <AlertDialogTitle>Discard this draft?</AlertDialogTitle>
    <AlertDialogDescription>You cannot undo it.</AlertDialogDescription>
    <AlertDialogFooter>
      <AlertDialogClose render={<Button emphasis="outline" />}>Cancel</AlertDialogClose>
      <AlertDialogClose render={<Button tone="danger" />}>Discard</AlertDialogClose>
    </AlertDialogFooter>
  </AlertDialogPopup>
</AlertDialog>
```
