---
id: "packages/active-ui/confirm-action-dialog"
title: "ConfirmActionDialog"
description: "A confirmation for an action that cannot be undone, with a cancel that is always available and a busy state while the action runs."
url: "/packages/active-ui/confirm-action-dialog"
locale: "en"
revision: "f8c566f305a7b65a"
---

# ConfirmActionDialog

Ask for confirmation before an action the reader cannot take back: deleting, publishing,
recalculating. `ConfirmActionDialog` asks one question with two answers, and stays open, busy,
while your action runs.

**Example: Confirm, cancel and the busy state**

Client: Runs entirely in your browser. Source: `examples/ui/confirm-action-dialog.tsx`.

```tsx
// apps/docs/examples/ui/confirm-action-dialog.tsx
'use client';

import {useState} from 'react';

import {ConfirmActionDialog} from '@workspace/active-ui/components/composed/confirm-action-dialog';
import {Button} from '@workspace/active-ui/components/primitives/button';

/**
 * Two confirmations. The application owns the open and busy state and runs
 * the action itself; here the deletion takes two seconds, so the busy state
 * is visible: a spinner, and both buttons disabled.
 */
export default function ConfirmActionDialogExample() {
  const [publishOpen, setPublishOpen] = useState(false);
  const [deleteOpen, setDeleteOpen] = useState(false);
  const [deleting, setDeleting] = useState(false);
  const [log, setLog] = useState('Nothing confirmed yet.');

  return (
    <div className="flex flex-col gap-3">
      <div className="flex flex-wrap gap-2">
        <Button variant="outline" onClick={() => setPublishOpen(true)}>
          Publish scheme
        </Button>
        <Button variant="destructive" onClick={() => setDeleteOpen(true)}>
          Delete table
        </Button>
      </div>
      <p role="status" className="text-muted-foreground text-sm">
        {log}
      </p>

      <ConfirmActionDialog
        isOpen={publishOpen}
        isLoading={false}
        onClose={() => {
          setPublishOpen(false);
          setLog('Publishing cancelled.');
        }}
        onConfirm={() => {
          setPublishOpen(false);
          setLog('Scheme published.');
        }}
        title="Publish this scheme?"
        description="The scheme becomes visible to every employer in this administration."
        confirmButtonText="Publish"
        cancelButtonText="Cancel"
      />

      <ConfirmActionDialog
        isOpen={deleteOpen}
        isLoading={deleting}
        onClose={() => {
          setDeleteOpen(false);
          setLog('Deletion cancelled.');
        }}
        onConfirm={() => {
          setDeleting(true);
          window.setTimeout(() => {
            setDeleting(false);
            setDeleteOpen(false);
            setLog('Table deleted.');
          }, 2000);
        }}
        title="Delete this lookup table?"
        description="This cannot be undone. Schemes that refer to the table keep their last calculated values."
        confirmButtonText="Delete"
        confirmButtonVariant="destructive"
        loadingText="Deleting…"
        cancelButtonText="Cancel"
      />
    </div>
  );
}
```

## What the composition adds

It is the alert dialog primitive, two buttons and a spinner, arranged so that every confirmation
behaves the same:

- an **alert dialog**: it must be answered; it is announced as an alert, not as a passing window;
- **cancel is the easy exit**: the cancel button and `Escape` call `onClose`; a click outside does
  not close an alert dialog, so the reader answers it deliberately;
- a **busy state**: with `isLoading`, the confirm button shows a spinner and `loadingText` (or its
  own label), and both buttons are disabled, so the action cannot run twice;
- one **colour axis**: only the confirm button changes, with `confirmButtonVariant`; the dialog
  itself carries no warning colour.

## Usage

```tsx
import {ConfirmActionDialog} from '@workspace/active-ui/components/composed/confirm-action-dialog';

<ConfirmActionDialog
  isOpen={open}
  isLoading={deleting}
  onClose={() => setOpen(false)}
  onConfirm={deleteTable}
  title="Delete this lookup table?"
  description="This cannot be undone."
  confirmButtonText="Delete"
  confirmButtonVariant="destructive"
  cancelButtonText="Cancel"
/>;
```

## What your application provides

- The **open state** (`isOpen`, `onClose`): the dialog has no trigger of its own; open it from your
  button.
- The **action** (`onConfirm`) and its **busy state** (`isLoading`). The dialog does not close
  itself on confirm: close it when your action has finished, or keep it open to show an error.
- All **text**: the title, the description, both button labels and, optionally, `loadingText`. The
  package supplies no default wording.

## Accessibility and keyboard

- Opening moves the focus into the dialog; `Tab` stays inside it.
- `Escape` cancels, and the focus returns to the element that opened it.
- The title names the dialog and the description is read with it.
- While busy, both buttons are disabled; the spinner is decorative because the label already says
  what happens.

## API

### ConfirmActionDialog

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `cancelButtonText` | `string` | yes | The text of the cancel button. |
| `confirmButtonText` | `string` | yes | The text of the confirm button, which names the action. |
| `description` | `ReactNode` | yes | What will happen, read with the title. Empty (`''` or `null`), the dialog has no description. |
| `isLoading` | `boolean` | yes | Whether the action runs. Both buttons are disabled and the confirm button shows a spinner beside `loadingText`. |
| `isOpen` | `boolean` | yes | Whether the dialog is open. Closed, it renders nothing. |
| `onClose` | `() => void` | yes | Called when the dialog asks to close: the cancel button or `Escape`. Set `isOpen` to `false` in response. |
| `onConfirm` | `() => void` | yes | Called when the confirm button is pressed. The dialog stays open: start the action, set `isLoading` while it runs, and close the dialog when it has finished. |
| `title` | `string` | yes | The title of the dialog, which also names it for assistive technology. |
| `confirmationNotice` | `string` | no | A status line below the description, for a dialog that stays open while something happened outside it, such as a confirmation in another step that has just succeeded. Empty, there is no line.  It is a `role="status"` region beside the description, not part of it: a description that changes while the dialog is open is not announced again, a status line is. |
| `confirmButtonVariant` | `ButtonVariant` | no | The variant of the confirm button; `destructive` for an action that deletes. |
| `loadingText` | `string` | no | The text of the confirm button while the action runs, such as "Deleting…".  Omitted, `confirmButtonText` stays, with the spinner beside it. There is no English default: this package does not translate, and the spinner already shows the busy state. |

**ConfirmActionDialogProps**

```ts
interface ConfirmActionDialogProps {
  isOpen: boolean;
  onClose: () => void;
  onConfirm: () => void;
  isLoading: boolean;
  title: string;
  description: React.ReactNode;
  confirmButtonText: string;
  confirmButtonVariant?: ButtonVariant;
  cancelButtonText: string;
  loadingText?: string;
  confirmationNotice?: string;
}
```
