<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# ConfirmDialog

Asks the user to confirm an action, with Cancel focused first. It is always a centered dialog, on phones too, and an outside press does not dismiss it. The actions stack full width on phones, the confirm action at the bottom, nearest the thumb.

```tsx
import { ConfirmDialog } from "@/components/ouro/dialog/confirm-dialog";
```

## Example

```tsx
<ConfirmDialog
  title="Delete recipe?"
  description="This can't be undone."
  confirmLabel="Delete"
  tone="destructive"
  onConfirm={() => deleteRecipe(id)}
  trigger={<Button tone="destructive" variant="soft">Delete</Button>}
/>
```

## Usage

Use `tone="destructive"` for destructive actions, and return a promise from `onConfirm` to show progress until the action finishes. Cancel and Escape close the dialog even while the action runs, and its result is then ignored. Cancel does not stop an action that has already started; make `onConfirm` cancellable or idempotent if that matters.

It stacks above the page at `--ouro-layer`, a z-index of 50 by default (from ouro-base.css). If your app has fixed bars above z-50, raise it in a `:root` rule of your own after the Ouro tokens, outside any `@layer`: `:root { --ouro-layer: 60; }`. Don't edit the `--ouro-layer: 50` line shadcn wrote; `shadcn add` resets it.

Testing accessibility: in WebKit, Base UI's focus guards take `role="button"` without a name so VoiceOver's cursor stays trapped. Exclude them: `new AxeBuilder({ page }).exclude("[data-base-ui-focus-guard]")`.

## Use instead when

- Use `Dialog` instead when the user has more to do than confirm or cancel.

## Props

The props the modes share. Type: `ConfirmDialogProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `cancelLabel` | `string` | `"Cancel"` | The cancel button's label. Cancel has focus when the dialog opens. |
| `confirmLabel` (required) | `string` |  | The confirm button's label: the action, not "OK" (`confirmLabel="Delete"`). |
| `description` | `ReactNode` |  | What happens if the user confirms: `description="This can't be undone."`. |
| `finalFocus` | `RefObject<HTMLElement \| null>` |  | Where focus goes after a confirmed action closes the confirmation. Any other close (Cancel, Escape, or your app setting `open` to false) returns focus to the trigger, even after an earlier confirm. When `onConfirm` removes the trigger, point this at what replaces it (give a heading `tabIndex={-1}`): `finalFocus={headingRef}`. |
| `onConfirm` (required) | `() => void \| Promise<void>` |  | Runs the action. Return a promise to keep the dialog open while it runs: the confirm button shows `loading` until it settles. When it resolves the dialog closes; when it rejects (or `onConfirm` throws) the dialog stays open so the user can retry or cancel. Cancel and Escape still close it while pending, so a stuck action never traps the user, and the result is then ignored. Cancel does not stop an action that has already started; make `onConfirm` cancellable or idempotent if that matters. If the action removes the trigger (a delete), pass `finalFocus` to what replaces it, such as the next item or the page heading. |
| `title` (required) | `string` |  | The question, which names the dialog: `title="Delete recipe?"`. |
| `tone` | `"primary" \| "destructive"` | `"primary"` | The confirm button's color role. Use `destructive` for destructive actions. |

## Modes

`ConfirmDialogProps` is a union: pass the props of exactly one mode. Mixing them is a type error.

### Controlled: `ConfirmDialogControlledProps`

A controlled ConfirmDialog: you own `open` and update it in `onOpenChange`.

| Prop | Type | Description |
| --- | --- | --- |
| `onOpenChange` (required) | `(open: boolean) => void` | Called with `false` when the user cancels (Cancel or Escape) and when `onConfirm` finishes. |
| `open` (required) | `boolean` | Whether the confirmation is open. |

Not in this mode (typed `never`):

- `trigger`: Not with `open`. Remove `open` and `onOpenChange` to let a trigger open the confirmation.

### With trigger: `ConfirmDialogWithTriggerProps`

An uncontrolled ConfirmDialog: `trigger` opens it.

| Prop | Type | Description |
| --- | --- | --- |
| `trigger` (required) | `ReactElement` | The element that opens the confirmation, usually a Button: `trigger={<Button tone="destructive">Delete</Button>}`. |

Not in this mode (typed `never`):

- `onOpenChange`: Not with `trigger`. Remove `trigger` to control the confirmation with `open` and `onOpenChange`.
- `open`: Not with `trigger`. Remove `trigger` to control the confirmation with `open` and `onOpenChange`.

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `cancelText`: Not a prop. Ouro UI uses `cancelLabel`: `<ConfirmDialog cancelLabel="Keep">`.
- `confirmText`: Not a prop. Ouro UI uses `confirmLabel`: `<ConfirmDialog confirmLabel="Delete">`.
- `destructive`: Not a prop. Ouro UI uses `tone`: `<ConfirmDialog tone="destructive">`.
- `isOpen`: Not a prop. Ouro UI uses `open` with `onOpenChange`: `<ConfirmDialog open={open} onOpenChange={setOpen}>`.
- `onCancel`: Not a prop. Cancel calls `onOpenChange(false)`: `<ConfirmDialog open={open} onOpenChange={setOpen}>`.
- `onClose`: Not a prop. `onOpenChange(false)` fires on close: `<ConfirmDialog open={open} onOpenChange={setOpen}>`.
- `variant`: Not a prop. Ouro UI uses `tone`: `<ConfirmDialog tone="destructive">`.
