# Alert Dialog

> A React alert dialog for confirmations: an async confirm that loads, fails inline and shakes, plus an await confirm() API.

- Type: React component (Overlays)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add alert-dialog`
- Docs: https://wingo-ui.com/components/alert-dialog

## Interaction

Press the trigger and the page dims while the card rises into the center on a smooth spring, its icon popping in a beat later. Focus lands on Cancel, the safe choice; Tab moves between the two buttons, Enter presses the focused one and Escape cancels. Clicking the dimmed page does nothing, because the question has to be answered. When the confirm returns a promise the action shows a spinner, Cancel and Escape wait, and the dialog closes once the request succeeds; if it fails the card shakes, the error slides in above the buttons and the action is ready to retry. In a type-to-confirm field Enter confirms as soon as the action is enabled. When it closes, focus goes back to the trigger, or to whatever had it before a confirm() call or a trigger-less open. On a phone it stays a centered card with the buttons stacked full width, the action on top and 44px tall. With reduced motion it only fades.

## Installation

```bash
npx wingo-ui@latest add alert-dialog
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/alert-dialog` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/ui/alert-dialog.tsx`

npm dependencies: `npm i @radix-ui/react-alert-dialog motion lucide-react`

Wingo UI dependencies (installed automatically):

- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
import type { ReactNode } from "react"
import { Trash2 } from "lucide-react"
import { AlertDialog, AlertDialogProvider, confirm } from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"

async function deleteListing(id: string) {
  const res = await fetch(`/api/listings/${id}`, { method: "DELETE" })
  if (!res.ok) throw new Error("Could not delete the listing. Try again.")
}

export function DeleteListing({ id }: { id: string }) {
  return (
    <AlertDialog
      trigger={
        <Button variant="soft" tone="danger" leftIcon={<Trash2 />}>
          Delete
        </Button>
      }
      tone="danger"
      title="Delete this listing?"
      description="The listing “Linus city bike” and its 12 messages will be deleted for good."
      confirmLabel="Delete"
      cancelLabel="Cancel"
      confirmLoadingText="Deleting"
      onConfirm={() => deleteListing(id)}
    />
  )
}

// the imperative api: mount the provider once, then await an answer anywhere
export function App({ children }: { children: ReactNode }) {
  return (
    <>
      {children}
      <AlertDialogProvider defaults={{ cancelLabel: "Cancel" }} />
    </>
  )
}

export async function signOutEverywhere() {
  const ok = await confirm({
    title: "Sign out of every device?",
    description: "You stay signed in on this phone only.",
    confirmLabel: "Sign out",
  })
  if (ok) console.log("signed out")
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title?` | `ReactNode` | `""` | The question, phrased so the buttons answer it. Without one the dialog asks "Are you sure?", so a screen reader always has a title. |
| `description?` | `ReactNode` | `""` | The consequence, in a sentence or two: what will be lost and whether it can be undone. |
| `confirmLabel?` | `string` | `"Continue"` | The action. Name what it does ("Delete listing"), not "Yes" or "OK". |
| `cancelLabel?` | `string` | `"Cancel"` | The safe way out. It gets the focus when the dialog opens. |
| `confirmLoadingText?` | `string` | `""` | Next to the spinner while onConfirm runs, like "Deleting". The button reserves room for it, so it never changes width. |
| `errorText?` | `ReactNode` | `""` | Shown above the buttons when onConfirm rejects or throws. When empty, the error's own message is used if it has one, then "Something went wrong. Try again." |
| `hideTitle?` | `boolean` | `false` | Keeps the title for screen readers only, for a dialog whose description says it all. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | Colors the action and the icon tile. danger for anything destructive (with a trash icon), warning for unsaved changes, success for a positive step; neutral is black in light mode and white in dark mode. |
| `color?` | `string` | `""` | Any CSS color for the action and the icon tile, replacing the tone. Text on the button turns black or white by lightness. |
| `size?` | `"sm" \| "md"` | `"sm"` | Max width 400px (sm) or 480px (md) for longer text. On a phone it is always 16px clear of both edges. |
| `radius?` | `"none" \| "sm" \| "md" \| "lg" \| "full"` | `"lg"` | Corner shape of the card: 0, 12, 16 or 24px. The icon tile and the buttons follow along, and full turns them into a circle and pills. |
| `align?` | `"start" \| "center"` | `"start"` | start reads like a desktop dialog with the buttons on the right; center stacks icon and text in the middle and splits the buttons evenly, like an iOS alert. |
| `defaultOpen?` | `boolean` | `false` | Starts open while uncontrolled. The playground reopens it on change so every setting shows. |
| `confirmDisabled?` | `boolean` | `false` | Blocks the action, e.g. until the listing's name is typed. Enter in a text field confirms once it is enabled. |
| `open?` | `boolean` |  | Controlled open state; pair it with onOpenChange. |
| `onOpenChange?` | `(open: boolean) => void` |  | Called when it opens or closes, whether by the trigger, Cancel, Escape or a finished confirm. |
| `trigger?` | `ReactNode` |  | The element that opens it, rendered with asChild: a single element that takes a ref, usually a Button. |
| `icon?` | `ReactNode` |  | Shown in a 40px tile above the title. Defaults to a trash can for danger, a triangle for warning and a check for success, and none for the rest; null hides it. |
| `children?` | `ReactNode` |  | Extra content between the text and the buttons, like a type-to-confirm field. It scrolls when the screen is short. |
| `onConfirm?` | `() => (void \| Promise<unknown>)` |  | Runs on the action. Return a promise to keep the dialog open with the action loading: resolving closes it, rejecting (or throwing) shows the error and shakes the card. A late result after the dialog closed is ignored. |
| `onCancel?` | `() => void` |  | Called on Cancel or Escape, not when the dialog closes after a confirm. |
| `container?` | `HTMLElement \| null` |  | Where the portal mounts. document.body by default. |
| `classNames?` | `Partial<Record<"overlay" \| "content" \| "icon" \| "header" \| "title" \| "description" \| "body" \| "error" \| "footer" \| "cancel" \| "action", string>>` |  | Extra classes for the inner parts. |
| `className?` | `string` |  | Extra classes for the card, merged last. The rest of the props (id, aria-*, onOpenAutoFocus...) go to it too. |

## Parts

### confirm (function)

Asks from anywhere, even outside React: resolves true once confirmed (after onConfirm settled) and false when cancelled. Takes every AlertDialog prop except trigger and the open state. Calls made while one is open wait their turn, and focus returns to what had it when confirm() was called. Needs <AlertDialogProvider /> mounted; without it, development warns and the browser's confirm steps in. Signature: (options: ConfirmOptions) => Promise<boolean>.

### useConfirm (hook)

The same confirm function, for code that prefers a hook. Signature: () => typeof confirm.

### AlertDialogProvider (component)

Mount once near the root, next to the toaster. Renders confirm() calls one at a time; defaults go under every call's options (translated labels, a house tone). If the last provider unmounts, calls still waiting resolve false. Props: { defaults?: ConfirmOptions }.

### AlertDialogRoot (component)

The parts API, for custom layouts: AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogBody, AlertDialogError, AlertDialogFooter, AlertDialogCancel and AlertDialogAction. Props: { open?; defaultOpen?; onOpenChange?; onCancel?; children }.

### AlertDialogTrigger (component)

Opens it. Use asChild with a Button; focus returns here when the dialog closes. Props: Radix AlertDialog.Trigger props.

### AlertDialogContent (component)

The overlay and the card. Takes size, tone, color, radius, align, container and classNames { overlay, content }, plus Radix Content props (onOpenAutoFocus, onCloseAutoFocus...). Props: AlertDialogContentProps.

### AlertDialogHeader (component)

Title and description, with an optional icon tile above them in the content's tone. Scrolls on its own when the text outgrows a short screen. Props: { icon?: ReactNode; classNames?: { icon }; children } & ComponentProps<"div">.

### AlertDialogIcon (component)

The 40px tile in the tone's soft color that pops in on the bouncy spring. The header renders it for you when you pass icon. Takes span props.

### AlertDialogTitle (component)

The question, 18px semibold. Required by screen readers; hide it with the sr-only class if the design has none. Props: Radix AlertDialog.Title props.

### AlertDialogDescription (component)

The consequence in muted text. Leave it out and pass aria-describedby={undefined} to the content. Props: Radix AlertDialog.Description props.

### AlertDialogBody (component)

Extra content between the text and the buttons (a type-to-confirm field). Scrolls when the screen is short. Takes div props.

### AlertDialogFooter (component)

The buttons: stacked full width below 640px with the action on top, a right-aligned row above (split evenly when align is center). Takes div props.

### AlertDialogAction (component)

The confirm Button in the content's tone. Runs onConfirm with the loading, error and shake behavior; call event.preventDefault() in onClick to keep it from running. Props: ButtonProps & { onConfirm?; errorText? }.

### AlertDialogCancel (component)

The soft Cancel Button. Focused on open, disabled while a confirm runs. Props: ButtonProps.

### AlertDialogError (component)

Shows the failed confirm's message with role alert; children replace it. Takes p props.

### ConfirmOptions (type)

What confirm() takes: the AlertDialog props without trigger, open, defaultOpen and onOpenChange.

## Examples

- **Async delete that can fail**: A promise keeps it open and loading; a rejection shakes the card, shows the message and lets you retry.
- **Imperative confirm()**: Mount AlertDialogProvider once, then await an answer from any handler. No dialog markup at the call site.
- **Type to confirm**: confirmDisabled until the word matches, in any case. Enter in the field confirms.
- **Brand tone for a paid step**: Non-destructive, with a custom icon and the price in the question and on the button.
- **Unsaved changes**: warning brings its own icon; size md gives two long labels room to sit side by side.
- **Queued confirms**: Two confirm() calls at once show one after the other, each resolving on its own.
- **Centered, like iOS**: align="center" with radius="full": a round icon tile and two pills that share the width.

## See also

- [Dialog](https://wingo-ui.com/components/dialog) ([Markdown](https://wingo-ui.com/components/dialog.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Delete Button](https://wingo-ui.com/components/delete-button) ([Markdown](https://wingo-ui.com/components/delete-button.md))
- [Toast](https://wingo-ui.com/components/toast) ([Markdown](https://wingo-ui.com/components/toast.md))
- [Alert](https://wingo-ui.com/components/alert) ([Markdown](https://wingo-ui.com/components/alert.md))

---

Canonical: https://wingo-ui.com/components/alert-dialog
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
