<!-- Generated by tools/docs.php from docs/src/dialog.html - edit the source, not this file. -->

# Dialogs

The browser's own modal `<dialog>`: opened by a command, the page behind it inert and dimmed, closed by its buttons, `Esc` or a click outside. And `REDZ.ui.confirm()` for a question asked from script.

## Examples

A button opens the dialog with `commandfor` + `command="show-modal"`; buttons inside close it with `command="close"`. `closedby="any"` lets a click outside close it too. The focus goes into the dialog - to the control marked `autofocus` - and back to the button when it closes.

```html
<button class="btn" type="button" commandfor="dialog-edit-note" command="show-modal"><i class="bi bi-chat-left-text" aria-hidden="true"></i>Add a note</button>
<dialog id="dialog-edit-note" class="dialog" closedby="any" aria-labelledby="dialog-edit-note-title">
	<div class="dialog-header">
		<h2 class="dialog-title" id="dialog-edit-note-title">A note for the guides</h2>
		<button class="btn-close" type="button" commandfor="dialog-edit-note" command="close" aria-label="Close"></button>
	</div>
	<div class="dialog-body vstack gap-8">
		<label class="form-field"><span class="form-label">Note</span><textarea class="form-control" autofocus>Pick up at the pier at 8:30.</textarea></label>
	</div>
	<div class="dialog-footer">
		<button class="btn" type="button" commandfor="dialog-edit-note" command="close">Cancel</button>
		<button class="btn btn-primary" type="button" commandfor="dialog-edit-note" command="close">Save</button>
	</div>
</dialog>
```

A product tints the head of every dialog with `--redz-dialog-header-bg`; `--redz-dialog-header-end` gives it room below and `--redz-dialog-header-line` a line under it.

### Sizes

`dialog-sm` for a question, the default for a form, `dialog-lg` for a wide table or a preview. A dialog is a region, so the width prefixes inside it measure the dialog: `sm:` fires in the default one, and `md:` in `dialog-lg`, 800 px wide.

```html
<button class="btn" type="button" commandfor="dialog-sizes-sm" command="show-modal">Small</button>
<button class="btn" type="button" commandfor="dialog-sizes-lg" command="show-modal">Large</button>
<dialog id="dialog-sizes-sm" class="dialog dialog-sm" aria-labelledby="dialog-sizes-sm-title">
	<div class="dialog-header"><h2 class="dialog-title" id="dialog-sizes-sm-title">Publish now?</h2></div>
	<div class="dialog-body"><p>The page goes live on three sites.</p></div>
	<div class="dialog-footer">
		<button class="btn" type="button" commandfor="dialog-sizes-sm" command="close" autofocus>Cancel</button>
		<button class="btn btn-primary" type="button" commandfor="dialog-sizes-sm" command="close">Publish</button>
	</div>
</dialog>
<dialog id="dialog-sizes-lg" class="dialog dialog-lg" closedby="any" aria-labelledby="dialog-sizes-lg-title">
	<div class="dialog-header">
		<h2 class="dialog-title" id="dialog-sizes-lg-title">History of changes</h2>
		<button class="btn-close" type="button" commandfor="dialog-sizes-lg" command="close" aria-label="Close"></button>
	</div>
	<div class="dialog-body"><p>Edited yesterday by M. K.; published on three sites; the Thai translation updated today.</p></div>
</dialog>
```

### A confirmation from script

`await REDZ.ui.confirm({title, text, ok, tone})` builds an `alertdialog`, waits for the answer and removes it: `true` for the confirming button, `false` for Cancel, `Esc` or a click outside. The focus starts on Cancel; the button words come from the caller or the page's texts.

```html
<button class="btn btn-danger" type="button" data-docs-run="confirm"><i class="bi bi-trash3" aria-hidden="true"></i>Delete 2 excursions</button>
<span class="text-muted fs-12" role="status" data-docs-answer></span>
```

For what cannot be undone, `phrase` asks the person to type a word first - the site's name, say: a field under the text, labelled by the page's text `confirm.phrase`, takes the focus, the confirming button waits until the words match, and `Enter` in the field confirms then.

```html
<button class="btn btn-danger" type="button" data-docs-run="confirm-phrase"><i class="bi bi-trash3" aria-hidden="true"></i>Delete the site</button>
<span class="text-muted fs-12" role="status" data-docs-answer></span>
```

What a deletion takes with it is said by `note` and `sections`: `note: {tone, text}` is an alert under the text - `warning` by default, `danger`, `info` or `success` - and each of `sections: [{title, items}]` a title over a list, such as what goes and what stays. Every word is text, never HTML.

```html
<button class="btn btn-danger" type="button" data-docs-run="confirm-lists"><i class="bi bi-trash3" aria-hidden="true"></i>Delete the project</button>
<span class="text-muted fs-12" role="status" data-docs-answer></span>
```

```js
await REDZ.ui.confirm({
  title: 'Delete the project Riverside tower?',
  note: {tone: 'danger', text: 'This cannot be undone.'},
  sections: [
    {title: 'Will be deleted:', items: ['128 tasks', '342 files', '1,204 log entries']},
    {title: 'Kept:', items: ['The audit history']},
  ],
  ok: 'Delete', tone: 'danger', phrase: 'Riverside tower',
});
```

### A question for one value

`await REDZ.ui.prompt({title, text, label, value, placeholder, type, inputmode, pattern, required, ok, cancel, tone})` asks for one value in a modal dialog and answers it, or `null` for Cancel and `Esc`. The field takes the focus with its text selected; `Enter` answers once the field is valid - `required`, `pattern` and `type` are held by the browser's own checks. The field is labelled by `label`, or the title; the buttons' words come from the caller or the page's texts.

```html
<button class="btn" type="button" data-docs-run="prompt"><i class="bi bi-pencil" aria-hidden="true"></i>Rename the folder</button>
<span class="text-muted fs-12" role="status" data-docs-answer></span>
```

```js
const name = await REDZ.ui.prompt({title: 'Rename the folder', label: 'Name', value: 'Tours 2026', required: true, ok: 'Rename'});
if (name !== null) {
  // send it
}
```

### On a phone and full screen

On any window a long dialog keeps its header and its footer in view: only `dialog-body` scrolls between them. A `<form>` may hold all three - `<form method="dialog">` right inside the dialog, its submit buttons in the footer - and the body still scrolls alone. On a window narrower than 576 px a dialog fills the screen by itself, while `dialog-sm` and a confirmation stay a card in the middle. `dialog-fullscreen` fills the screen on any window.

```html
<button class="btn" type="button" commandfor="dialog-full-panel" command="show-modal"><i class="bi bi-arrows-fullscreen" aria-hidden="true"></i>Open full screen</button>
<dialog class="dialog dialog-fullscreen" id="dialog-full-panel" aria-labelledby="dialog-full-title">
	<div class="dialog-header">
		<h2 class="dialog-title" id="dialog-full-title">Route of the day</h2>
		<button class="btn-close" type="button" commandfor="dialog-full-panel" command="close" aria-label="Close"></button>
	</div>
	<div class="dialog-body"><p>Pick-up at the hotel at 7:30, the pier at 8:15, North Bay at 9:30; snorkeling, the emerald lake, lunch on the boat, back by 17:00.</p></div>
	<div class="dialog-footer"><button class="btn btn-primary" type="button" commandfor="dialog-full-panel" command="close">Done</button></div>
</dialog>
```

## Keyboard and accessibility

- `Tab` stays inside an open dialog; `Esc` closes it; the focus goes back to the button that opened it.
- The dialog is named by its title through `aria-labelledby`. A question that must be answered is `role="alertdialog"` with its focus on the safe answer.
- The page behind does not scroll while a dialog is open, and keeps its width: the scrollbar's place stays.
- A notice raised while a dialog is open shows inside the dialog, since everything outside it is inert.
- Where the browser lacks invoker commands (below Chrome 135, Firefox 144, Safari 26.2) or `closedby` (below Chrome 134, Firefox 141, and in Safari), the library's script stands in.

## Tokens

From [`css/components/dialog.css`](https://github.com/redz-tech/REDZ.UI/blob/main/css/components/dialog.css), which declares no custom property of its own.

It reads the theme's `--redz-border`, `--redz-radius-16`, `--redz-bg-raised`, `--redz-shadow-3`, `--redz-space-12`, `--redz-space-16`, `--redz-space-24`, `--redz-dialog-header-end`, `--redz-dialog-header-line`, `--redz-dialog-header-bg`, `--redz-fs-16`, `--redz-text-muted`, `--redz-space-8`, `--redz-bg-page`, `--redz-ease`.

## From Bootstrap 5.3

| Bootstrap 5.3 | REDZ.UI | Change | Note |
| --- | --- | --- | --- |
| `modal` | `dialog` | rename | on a <dialog>, opened by commandfor + command="show-modal" |
| `modal-backdrop` | - | remove | the browser's ::backdrop |
| `modal-body` | `dialog-body` | rename |  |
| `modal-content` | - | remove | no wrappers: the <dialog> is the box |
| `modal-dialog` | - | remove | no wrappers: the <dialog> is the box |
| `modal-dialog-centered` | - | remove | a dialog is centered |
| `modal-dialog-scrollable` | - | remove | the dialog's body scrolls by itself |
| `modal-footer` | `dialog-footer` | rename |  |
| `modal-fullscreen` | `dialog-fullscreen` | rename |  |
| `modal-header` | `dialog-header` | rename |  |
| `modal-static` | - | remove | closedby="closerequest" |
| `modal-title` | `dialog-title` | rename |  |
| `data-bs-toggle="modal"` | `commandfor="ID" command="show-modal"` | trigger | on the button; the dialog is a <dialog id="ID"> |
| `data-bs-dismiss="modal"` | `commandfor="ID" command="close"` | trigger |  |
| `data-bs-backdrop="static"` | `closedby="closerequest"` | trigger | Esc closes it, a click outside does not |
| `data-bs-keyboard="false"` | `closedby="none"` | trigger | only its buttons close it |
| `bootstrap.Modal` | `dialog.showModal() and dialog.close()` | trigger |  |
| `show.bs.modal` | `toggle` | review | the dialog's toggle event, or code right after showModal() |
| `shown.bs.modal` | `toggle` | review | the dialog's toggle event, or code right after showModal() |
| `hide.bs.modal` | `cancel` | review | the dialog's cancel event (Esc); your buttons close it |
| `hidden.bs.modal` | `close` | rename | the dialog's close event; dialog.returnValue holds the closing button's value |
