Overlaysstable.dialog
Confirm dialog
A modal question before an irreversible or external action, naming its consequences and the action as the button label.
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
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
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.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | required | — | Id of the <dialog>; prefix for {id}-title and {id}-body. (confirmDialog() generates ch-confirm-{n} itself.)pattern ^[A-Za-z][\w-]*$ |
title | string | required | — | The question, with the action verb ("Prüfung abschliessen?"). min 1 chars |
body | string | optional | — | What will happen, in plain words; end with "Das kann nicht rückgängig gemacht werden." when true. |
consequences | array of string | optional | — | Effects as a list, with numbers (dry-run result). |
confirmLabel | string | required | — | Confirm button: verb + object, ideally with the count ("23 Gurte eintragen"). min 1 chars |
cancelLabel | string | optional | "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
| Attribute | Values | Default | Description |
|---|---|---|---|
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
| Part | Description |
|---|---|
.dialog__header > h2 | The question (no ✕ button — Esc and "Abbrechen" close it). |
.dialog__body | Body paragraph. |
.dialog__consequence | Grey 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
| State | Description |
|---|---|
[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
| Function | Signature | Returns | Description |
|---|---|---|---|
confirmDialog | confirmDialog({ title: string, body?: string, consequences?: string[], confirmLabel: string, cancelLabel?: string = "Abbrechen", tone?: "primary" | "danger" = "primary" }) | Promise<boolean> — true when confirmed | Builds this markup, appends it to <body>, opens it modally and removes it after closing. Resolves false on cancel, Esc or backdrop click. |
openDialog | openDialog(dialog: HTMLDialogElement, opener?: HTMLElement) | void | Opens a rendered confirm dialog; focus returns to opener on close. |
Events
| Event | Detail | Description |
|---|---|---|
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
confirm().