RZREDZ.UI

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.

A note for the guides

<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.

Publish now?

The page goes live on three sites.

History of changes

Edited yesterday by M. K.; published on three sites; the Thai translation updated today.

<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.

<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.

<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.

<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>
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.

<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>
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.

Route of the day

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.

<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, 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.3REDZ.UIChangeNote
modaldialogrenameon a <dialog>, opened by commandfor + command="show-modal"
modal-backdrop-removethe browser's ::backdrop
modal-bodydialog-bodyrename
modal-content-removeno wrappers: the <dialog> is the box
modal-dialog-removeno wrappers: the <dialog> is the box
modal-dialog-centered-removea dialog is centered
modal-dialog-scrollable-removethe dialog's body scrolls by itself
modal-footerdialog-footerrename
modal-fullscreendialog-fullscreenrename
modal-headerdialog-headerrename
modal-static-removeclosedby="closerequest"
modal-titledialog-titlerename
data-bs-toggle="modal"commandfor="ID" command="show-modal"triggeron the button; the dialog is a <dialog id="ID">
data-bs-dismiss="modal"commandfor="ID" command="close"trigger
data-bs-backdrop="static"closedby="closerequest"triggerEsc closes it, a click outside does not
data-bs-keyboard="false"closedby="none"triggeronly its buttons close it
bootstrap.Modaldialog.showModal() and dialog.close()trigger
show.bs.modaltogglereviewthe dialog's toggle event, or code right after showModal()
shown.bs.modaltogglereviewthe dialog's toggle event, or code right after showModal()
hide.bs.modalcancelreviewthe dialog's cancel event (Esc); your buttons close it
hidden.bs.modalcloserenamethe dialog's close event; dialog.returnValue holds the closing button's value