Confirm dialog

Only for actions that can't be undone or that leave the system (UX guide §5.1): delete permanently, close a belt test, book invoices, send emails, write to the CRM. Reversible actions don't ask — they act and offer "Rückgängig" in a toast.

The title is the question with the verb ("Prüfung abschliessen?"). The body says in plain words what will happen; consequences lists the effects with numbers ("23 neue Gurte werden in Webling eingetragen"). The confirm button repeats the verb and the main number ("23 Gurte eintragen") — never "OK" or "Ja".

Normally created from page code with confirmDialog({...}) from js/dialog.js, which returns a Promise that resolves true on confirm. Focus starts on "Abbrechen" (the safe option), and Esc, backdrop click and "Abbrechen" all resolve false. render() produces the same markup statically, for documentation and server-rendered pages.

External actions also need a dry-run preview of their effects; render that preview as the consequences (UX guide §5.2). Browser confirm() is banned.

Examples

Close a belt test

Prüfung abschliessen?

Das kann nicht rückgängig gemacht werden.

  • 23 neue Gurte werden in Webling eingetragen.
  • 4 Personen haben nicht bestanden.
  • 23 Prüfungsgebühren kommen in die Abrechnung.
Data
render({
  "id": "confirm-close-exam",
  "title": "Prüfung abschliessen?",
  "body": "Das kann nicht rückgängig gemacht werden.",
  "consequences": [
    "23 neue Gurte werden in Webling eingetragen.",
    "4 Personen haben nicht bestanden.",
    "23 Prüfungsgebühren kommen in die Abrechnung."
  ],
  "confirmLabel": "23 Gurte eintragen"
})
Markup
<dialog class="dialog" id="confirm-close-exam" aria-labelledby="confirm-close-exam-title" aria-describedby="confirm-close-exam-body">
<form method="dialog" class="dialog__inner">
<header class="dialog__header"><h2 id="confirm-close-exam-title">Prüfung abschliessen?</h2></header>
<div class="dialog__body" id="confirm-close-exam-body">
<p>Das kann nicht rückgängig gemacht werden.</p>
<div class="dialog__consequence"><ul><li>23 neue Gurte werden in Webling eingetragen.</li><li>4 Personen haben nicht bestanden.</li><li>23 Prüfungsgebühren kommen in die Abrechnung.</li></ul></div>
</div>
<footer class="dialog__footer button-row" data-stack-mobile>
<button class="button" value="cancel" autofocus>Abbrechen</button>
<button class="button" data-variant="primary" value="confirm">23 Gurte eintragen</button>
</footer>
</form>
</dialog>

Delete permanently

Lektionsplan «Kinder Dienstag» löschen?

Der Plan und seine 12 Übungen werden endgültig gelöscht. Trainings, die ihn schon verwendet haben, bleiben erhalten.

Data
render({
  "id": "confirm-delete-plan",
  "title": "Lektionsplan «Kinder Dienstag» löschen?",
  "body": "Der Plan und seine 12 Übungen werden endgültig gelöscht. Trainings, die ihn schon verwendet haben, bleiben erhalten.",
  "confirmLabel": "Plan löschen",
  "tone": "danger"
})
Markup
<dialog class="dialog" id="confirm-delete-plan" aria-labelledby="confirm-delete-plan-title" aria-describedby="confirm-delete-plan-body">
<form method="dialog" class="dialog__inner">
<header class="dialog__header"><h2 id="confirm-delete-plan-title">Lektionsplan «Kinder Dienstag» löschen?</h2></header>
<div class="dialog__body" id="confirm-delete-plan-body">
<p>Der Plan und seine 12 Übungen werden endgültig gelöscht. Trainings, die ihn schon verwendet haben, bleiben erhalten.</p>
</div>
<footer class="dialog__footer button-row" data-stack-mobile>
<button class="button" value="cancel" autofocus>Abbrechen</button>
<button class="button" data-variant="danger" value="confirm">Plan löschen</button>
</footer>
</form>
</dialog>

Data contract

What render(data) accepts — validated against this JSON Schema in development and tests. Unknown fields are rejected. The same schema is in components.json.

FieldTypeRequiredDefaultDescription
idstringrequired—Id of the <dialog>; prefix for {id}-title and {id}-body. (confirmDialog() generates ch-confirm-{n} itself.)
pattern ^[A-Za-z][\w-]*$
titlestringrequired—The question, with the action verb ("Prüfung abschliessen?").
min 1 chars
bodystringoptional—What will happen, in plain words; end with "Das kann nicht rückgängig gemacht werden." when true.
consequencesarray of stringoptional—Effects as a list, with numbers (dry-run result).
confirmLabelstringrequired—Confirm button: verb + object, ideally with the count ("23 Gurte eintragen").
min 1 chars
cancelLabelstringoptional"Abbrechen"Cancel button text.
tone"primary" | "danger"optional"primary"danger for destroying something of value (delete); primary otherwise.
JSON Schema
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "id",
    "title",
    "confirmLabel"
  ],
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[A-Za-z][\\w-]*$",
      "description": "Id of the `<dialog>`; prefix for `{id}-title` and `{id}-body`. (`confirmDialog()` generates `ch-confirm-{n}` itself.)"
    },
    "title": {
      "type": "string",
      "minLength": 1,
      "description": "The question, with the action verb (\"Prüfung abschliessen?\")."
    },
    "body": {
      "type": "string",
      "description": "What will happen, in plain words; end with \"Das kann nicht rückgängig gemacht werden.\" when true."
    },
    "consequences": {
      "type": "array",
      "description": "Effects as a list, with numbers (dry-run result).",
      "items": {
        "type": "string",
        "minLength": 1,
        "description": "One effect (\"23 Prüfungsgebühren kommen in die Abrechnung\")."
      }
    },
    "confirmLabel": {
      "type": "string",
      "minLength": 1,
      "description": "Confirm button: verb + object, ideally with the count (\"23 Gurte eintragen\")."
    },
    "cancelLabel": {
      "type": "string",
      "default": "Abbrechen",
      "description": "Cancel button text."
    },
    "tone": {
      "type": "string",
      "enum": [
        "primary",
        "danger"
      ],
      "default": "primary",
      "description": "`danger` for destroying something of value (delete); `primary` otherwise."
    }
  }
}

Markup & states

Root: dialog.dialog — usable without render() by writing the markup directly.

Attributes

AttributeValuesDefaultDescription
aria-labelledby"{id}-title"—The question names the dialog.
aria-describedby"{id}-body"—Body and consequences are read as its description.
autofocus (on cancel)——Focus starts on the safe option.

Parts

PartDescription
.dialog__header > h2The question (no ✕ button — Esc and "Abbrechen" close it).
.dialog__bodyBody paragraph.
.dialog__consequenceGrey box with the list of effects.
.dialog__footer.button-row[data-stack-mobile]"Abbrechen" (value="cancel") then the confirm button (value="confirm"); stacked on phones with confirm at the bottom.

States

StateDescription
[open]Shown modally.

Behaviour & events

Module: js/dialog.js

import { confirmDialog } from '@clubhub/ui/js/dialog.js'; no markup needed. For rendered markup, open it with openDialog() or an open-dialog button and read returnValue on close.

API

FunctionSignatureReturnsDescription
confirmDialogconfirmDialog({ title: string, body?: string, consequences?: string[], confirmLabel: string, cancelLabel?: string = "Abbrechen", tone?: "primary" | "danger" = "primary" })Promise<boolean> — true when confirmedBuilds this markup, appends it to <body>, opens it modally and removes it after closing. Resolves false on cancel, Esc or backdrop click.
openDialogopenDialog(dialog: HTMLDialogElement, opener?: HTMLElement)voidOpens a rendered confirm dialog; focus returns to opener on close.

Events

EventDetailDescription
close (on the dialog)dialog.returnValue → "confirm" | "cancel"Native event when the rendered dialog closes.

Accessibility

  • Native modal dialog: focus trapped, Esc closes, focus returns to the opener.
  • Focus starts on "Abbrechen", never on a danger button (UX guide §5.5).
  • Title and consequences are announced as the dialog's name and description.

Guidance

DoAsk a question with the verb; answer it with the verb.
DoName numbers and consequences: who, how many, where it is written.
Don'tNo "Sind Sie sicher?" with [OK] [Abbrechen].
Don'tDon't confirm reversible actions — use undo.
Don'tNever use browser confirm().

Related: Dialog · Toast · Button