# Confirming an action

> Dialog, wording and what happens afterwards, when the action cannot be undone.

A confirmation is an interruption. It is worth it when the action cannot be undone, and only then.

## The markup

```html
<button class="lt-button" data-variant="danger" id="cancel-job" type="button">
  Cancel the job
</button>


<dialog class="lt-dialog" id="confirm" aria-labelledby="confirm-title">
  <h2 class="lt-dialog__title" id="confirm-title">Cancel job 222-0481?</h2>


  <div class="lt-dialog__body">
    <p>The crew keeps the advance. This cannot be undone.</p>
  </div>


  <form method="dialog" class="lt-dialog__footer">
    <button class="lt-button" data-variant="secondary" value="keep">Keep the job</button>
    <button class="lt-button" data-variant="danger" value="cancel">Cancel the job</button>
  </form>
</dialog>


<lt-toast id="messages" label="Notifications"></lt-toast>


<script type="module">
  import { defineLtToast } from
    "https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.40.0/dist/components/standalone/toast/lt-toast.js"


  defineLtToast()


  const dialog = document.getElementById("confirm")
  const messages = document.getElementById("messages")


  document.getElementById("cancel-job").addEventListener("click", () => {
    // Escape and close() without a value leave the previous value in place.
    dialog.returnValue = ""
    dialog.showModal()
  })


  dialog.addEventListener("close", async () => {
    if (dialog.returnValue !== "cancel") return


    await cancelJob()
    messages.show("Job 222-0481 has been cancelled", { color: "success" })
  })
</script>
```

`showModal()`, not `open`. Only that moves focus in, keeps focus in the dialog, closes on Escape and makes the rest of the page unreachable.

`<form method="dialog">` closes the dialog when a button is pressed, and puts the button’s `value` in `dialog.returnValue`. You need no listeners on the buttons, and Enter works as expected.

## Wording

| Where | Write | Do not write |
| - | - | - |
| The heading | The question: “Cancel job 222-0481?” | “Are you sure?”, “Confirm” |
| The body text | What happens: “The crew keeps the advance.” | “This action cannot be undone.” on its own |
| The button | The action: “Cancel the job” | “OK”, “Yes” |

The button text matters most. Someone who hears the dialog without seeing the heading hears only the buttons. “OK” says nothing about what is about to happen.

## Afterwards

The action is done, and the user needs to know. A toast in the corner is enough for something that went well:

```js
messages.show("Job 222-0481 has been cancelled", { color: "success" })
```

But the toast disappears. So:

- **The undo button does not belong there.** If the action should be undoable, the button belongs in the page, and then perhaps a confirmation was not what you needed.
- **Nor does an error.** If the cancellation failed, that is an [Alert](/components/alert.md) in the page, or a toast with `duration: 0` that stays.
- **Focus has to go somewhere.** The browser returns focus to the button that opened the dialog. If that button is gone, because the row it was in has been deleted, you have to move focus yourself, for example to the heading of the list.

## When not to confirm

If the action can be undone, undo is better than confirmation. An extra click every time costs more than an undo button for the few times someone gets it wrong.

Confirmation is right when:

- something disappears for good
- something is sent to others, such as an order or a message
- something costs money

Confirmation is wrong when the action is merely inconvenient to redo. Then you create a dialog everyone clicks away without reading, and the next time it matters, they do the same.

## What the system does not do

- **Preventing double clicks.** Reject a new press while the action is in progress, and show it with `aria-disabled="true"`. If you switch the button off with `disabled`, it loses the focus the browser just gave back to it when the dialog closed, and focus ends up on `<body>`.
- **Remembering the choice.** “Don’t ask me again” is a setting your app has to keep.
- **Deciding what is dangerous.** `data-variant="danger"` is a colour. What deserves it is a judgement.
