# ConfirmationDialog

> A ready-made confirm/cancel dialog with async loading and error handling, plus an imperative useConfirm() hook.

Category: Overlays · Status: stable · Package: @bindshq/stance-react@0.1.1 · Docs: https://stanceui.com/components/confirmation-dialog

ConfirmationDialog wraps AlertDialog with the props most confirmations need. When onConfirm returns a Promise, the confirm button shows a spinner, Cancel and Escape are blocked, the dialog closes on success and shows the error message on failure. ConfirmProvider and useConfirm() give you `await confirm({...})` from any event handler, which replaces window.confirm and hand-rolled confirm contexts.

## Import

```tsx
import { ConfirmationDialog, ConfirmProvider, useConfirm } from '@bindshq/stance-react';
```

Styles are imported once per app: `@bindshq/stance-tokens/css` and `@bindshq/stance-react/styles.css`.

## Examples

### Basic

```tsx
import { Button, ConfirmationDialog } from '@bindshq/stance-react';

export function Basic() {
  return (
    <ConfirmationDialog
      trigger={<Button variant="danger">Remove user</Button>}
      title="Remove Ada Lovelace?"
      description="Ada loses access to Binds immediately. Their signatures stay in place."
      confirmLabel="Remove user"
      tone="danger"
      onConfirm={() => {}}
    />
  );
}
```

### Async confirm

Return a Promise from onConfirm. This example fails once, then succeeds.

```tsx
import * as React from 'react';
import { Button, ConfirmationDialog } from '@bindshq/stance-react';

export function Async() {
  const attempts = React.useRef(0);
  return (
    <ConfirmationDialog
      trigger={<Button>Deploy signatures</Button>}
      title="Deploy to 128 mailboxes?"
      description="Signatures are pushed through your Exchange relay."
      confirmLabel="Deploy"
      loadingText="Deploying…"
      onConfirm={() =>
        new Promise<void>((resolve, reject) => {
          attempts.current += 1;
          setTimeout(() => {
            if (attempts.current % 2 === 1)
              reject(new Error('Exchange did not respond. Try again.'));
            else resolve();
          }, 1200);
        })
      }
    />
  );
}
```

### useConfirm()

Await the user’s decision inside any handler.

```tsx
import * as React from 'react';
import { Button, ConfirmProvider, useConfirm } from '@bindshq/stance-react';

function DeleteButton() {
  const confirm = useConfirm();
  const [status, setStatus] = React.useState('');
  return (
    <div style={{ display: 'grid', gap: 8, justifyItems: 'center' }}>
      <Button
        variant="outline"
        onClick={async () => {
          const ok = await confirm({
            title: 'Archive “Q3 campaign”?',
            description: 'Archived templates can be restored for 30 days.',
            confirmLabel: 'Archive',
          });
          setStatus(ok ? 'Archived.' : 'Kept.');
        }}
      >
        Archive template
      </Button>
      <span aria-live="polite">{status}</span>
    </div>
  );
}

export function Imperative() {
  return (
    <ConfirmProvider>
      <DeleteButton />
    </ConfirmProvider>
  );
}
```

## API reference

| Component | Prop | Type | Default | Description |
| --- | --- | --- | --- | --- |
| ConfirmationDialog | `title` (required) | `ReactNode` | None | Question naming the action and object. |
| ConfirmationDialog | `description` | `ReactNode` | None | Consequences of confirming. |
| ConfirmationDialog | `confirmLabel` | `ReactNode` | `'Confirm'` | Confirm button text. Prefer a verb. |
| ConfirmationDialog | `cancelLabel` | `ReactNode` | `'Cancel'` | Cancel button text. |
| ConfirmationDialog | `tone` | `'default' \| 'danger'` | `'default'` | Danger styles the confirm button as destructive. |
| ConfirmationDialog | `onConfirm` | `() => void \| Promise<unknown>` | None | Return a Promise for loading state; rejecting keeps the dialog open and shows the error message. |
| ConfirmationDialog | `onCancel` | `() => void` | None | Called on Cancel or Escape. |
| ConfirmationDialog | `loadingText` | `ReactNode` | None | Confirm label while pending. |
| ConfirmationDialog | `trigger` | `ReactElement` | None | Optional trigger, rendered with asChild. |
| ConfirmationDialog | `open / defaultOpen / onOpenChange` | `boolean / boolean / (open: boolean) => void` | None | Controlled or uncontrolled state. |
| ConfirmationDialog | `children` | `ReactNode` | None | Extra content above the buttons. |
| ConfirmProvider | `children` | `ReactNode` | None | Mount once near the root. |
| useConfirm | `returns` | `(options: ConfirmOptions) => Promise<boolean>` | None | Resolves true on confirm, false on cancel. Options: title, description, confirmLabel, cancelLabel, tone. |

## Accessibility

- Built on AlertDialog: role="alertdialog", focus starts on Cancel, outside clicks never dismiss.
- While pending the confirm button has aria-busy and Cancel is disabled, so the request cannot be abandoned half-way.
- Errors from a rejected promise are shown in a role="alert" message inside the dialog.

## Keyboard

| Keys | Action |
| --- | --- |
| `Tab` | Moves between Cancel and Confirm. |
| `Enter` `Space` | Activates the focused button. |
| `Esc` | Cancels (ignored while a confirm is pending). |

## Guidelines

**Do**

- Use tone="danger" and a specific verb (“Remove user”) for destructive actions.
- Throw an Error with a human message from onConfirm on failure.

**Don’t**

- Ask for confirmation of easily undone actions; offer Undo in a toast instead.

## Related

- https://stanceui.com/components/alert-dialog
- https://stanceui.com/components/toast
