# ConfirmDialog

> Package `@umriss-ui/core`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/core/confirmdialog/>. Every page in one line: [llms.txt](https://romanhaendler.github.io/umriss-ui/core/llms.txt); every page in full: [llms-full.txt](https://romanhaendler.github.io/umriss-ui/core/llms-full.txt).

A small modal window with one question and two buttons, for the moment before something that cannot be undone (also called a confirmation). Reach for it before deleting, rejecting or closing for good.

```ts
import { ConfirmDialog } from "@umriss-ui/core";
```

Demo page: https://romanhaendler.github.io/umriss-ui/core/confirmdialog/

Use it only where the step cannot be taken back: a question before every action gets clicked away, and then it no longer protects the one that mattered.

It does not close itself on `onConfirm`, so you can show `loading` while the request runs. Cancelling, by Escape, the cross or the second button, calls `onClose`.

## Examples

### The one question

The `description` says what cannot be undone, and `confirmLabel` names the action – “Delete” says what happens, “OK” does not.

```tsx
import { useState } from "react";
import { Button, ConfirmDialog, Stack, Text } from "@umriss-ui/core";

export default function TheOneQuestion() {
  const [open, setOpen] = useState(false);
  const [state, setState] = useState("Draft invoice INV-26-0319 exists.");

  return (
    <Stack gap={3} align="flex-start">
      <Button variant="danger" onClick={() => setOpen(true)}>
        Delete the draft
      </Button>
      <Text size="xs" tone="muted">
        {state}
      </Text>
      <ConfirmDialog
        open={open}
        onClose={() => setOpen(false)}
        onConfirm={() => {
          setState("Draft invoice INV-26-0319 is deleted.");
          setOpen(false);
        }}
        tone="danger"
        title="Delete draft invoice INV-26-0319?"
        description="Its three lines and the attached delivery note go with it. This cannot be undone."
        confirmLabel="Delete"
      />
    </Stack>
  );
}
```

### Ask before a commitment

Not every irreversible step destroys something: the `primary` tone asks before closing a month, which then accepts no postings.

```tsx
import { useState } from "react";
import { Button, ConfirmDialog, Stack, Text } from "@umriss-ui/core";

export default function ACommitment() {
  const [open, setOpen] = useState(false);
  const [closed, setClosed] = useState(false);

  return (
    <Stack gap={3} align="flex-start">
      <Button variant="primary" disabled={closed} onClick={() => setOpen(true)}>
        Close February
      </Button>
      <Text size="xs" tone="muted">
        February 2026 is {closed ? "closed" : "open for postings"}.
      </Text>
      <ConfirmDialog
        open={open}
        onClose={() => setOpen(false)}
        onConfirm={() => {
          setClosed(true);
          setOpen(false);
        }}
        tone="primary"
        title="Close February 2026?"
        description="Actuals for February can no longer be posted or corrected; late invoices go to March."
        confirmLabel="Close the month"
      />
    </Stack>
  );
}
```

### Stay open while it runs

The dialog does not close itself on `onConfirm`; set `loading` while the request runs and close it when it is done.

```tsx
import { useEffect, useState } from "react";
import { Button, ConfirmDialog, Stack, Text } from "@umriss-ui/core";

export default function WhileItRuns() {
  const [open, setOpen] = useState(false);
  const [running, setRunning] = useState(false);
  const [rejected, setRejected] = useState(false);

  useEffect(() => {
    if (!running) return;
    const timer = setTimeout(() => {
      setRunning(false);
      setRejected(true);
      setOpen(false);
    }, 1500);
    return () => clearTimeout(timer);
  }, [running]);

  return (
    <Stack gap={3} align="flex-start">
      <Button variant="danger" disabled={rejected} onClick={() => setOpen(true)}>
        Reject the invoice
      </Button>
      <Text size="xs" tone="muted">
        INV-26-0317 from Kettering &amp; Shaw Events is {rejected ? "rejected" : "awaiting approval"}.
      </Text>
      <ConfirmDialog
        open={open}
        onClose={() => setOpen(false)}
        onConfirm={() => setRunning(true)}
        loading={running}
        tone="danger"
        title="Reject invoice INV-26-0317?"
        description="The supplier is told by e-mail and must send a new invoice."
        confirmLabel="Reject"
      />
    </Stack>
  );
}
```

## When to use something else

- The user fills in fields before confirming → Modal
- The action can be undone afterwards → Do it, and confirm it with a [Toast](https://romanhaendler.github.io/umriss-ui/core/toast/)

## Keyboard

| Key | Action |
|---|---|
| `Tab / Shift+Tab` | Moves between the controls inside; focus does not leave the window. |
| `Escape` | Asks to close: `onClose` is called, and the window goes when you close it. |
| `Enter or Space` | Presses the focused button. |

## API

### `ConfirmDialogProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `open` *required* | `boolean` | — | Whether the query stands.<br>Shown in: [The one question](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#the-one-question), [Ask before a commitment](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#a-commitment), [Stay open while it runs](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#while-it-runs) and 3 more |
| `title` *required* | `ReactNode` | — | The question, as a statement: "Delete invoice 2041?".<br>Shown in: [The one question](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#the-one-question), [Ask before a commitment](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#a-commitment), [Stay open while it runs](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#while-it-runs) and 3 more |
| `description` | `ReactNode` | — | What about it cannot be taken back. The most important line of the dialog.<br>Shown in: [The one question](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#the-one-question), [Ask before a commitment](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#a-commitment), [Stay open while it runs](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#while-it-runs) and 3 more |
| `confirmLabel` | `string` | the wording's "Confirm" | Label of the agreeing button. Better is the verb of the action: "Delete" says what is about to happen.<br>Shown in: [The one question](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#the-one-question), [Ask before a commitment](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#a-commitment), [Stay open while it runs](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#while-it-runs) and 3 more |
| `cancelLabel` | `string` | the wording's "Cancel" | Label of the cancelling button.<br>Shown in: [Guard unsaved changes (Modal)](https://romanhaendler.github.io/umriss-ui/core/modal/#unsaved-changes) |
| `tone` | `"primary" \| "danger"` | `"primary"` | "danger" for destructive actions.<br>Shown in: [The one question](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#the-one-question), [Ask before a commitment](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#a-commitment), [Stay open while it runs](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#while-it-runs) and 2 more |
| `loading` | `boolean` | `false` | Locks the actions while the confirmation is running.<br>Shown in: [Stay open while it runs](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#while-it-runs) |
| `ref` | `Ref<HTMLDialogElement>` | — | Receives the component's root element. From `RefProp`.<br>Shown in: [A row that holds still (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#a-row-that-holds-still), [An anchored surface (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#anchored-surface), [Take the width of the trigger (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#width-from-anchor) and 2 more |

#### Events

| Prop | Type | Default | Description |
|---|---|---|---|
| `onClose` *required* | `() => void` | — | Cancelling: Escape, cross, backdrop or the second button.<br>Shown in: [The one question](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#the-one-question), [Ask before a commitment](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#a-commitment), [Stay open while it runs](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#while-it-runs) and 3 more |
| `onConfirm` *required* | `() => void` | — | Agreeing. The dialog does not close itself from that – whoever wants to show a confirmation in progress still needs it.<br>Shown in: [The one question](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#the-one-question), [Ask before a commitment](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#a-commitment), [Stay open while it runs](https://romanhaendler.github.io/umriss-ui/core/confirmdialog/#while-it-runs) and 3 more |

Also takes every attribute of `<dialog>` – without `children`, `onClose`, `open`, `title`.

## Known limits

- A title, a description and two buttons, nothing more: fields belong in a [Modal](https://romanhaendler.github.io/umriss-ui/core/modal/).

What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
