Dialog
A native modal <dialog>, opened and closed by
buttons with commandfor and command.
No script needed.
The markup: a dialog with a header, a div and a footer
The parts are direct children of <dialog>:
<header> (title, description, close button),
<div> (body) and <footer>
(buttons). Tsugi wraps the parts in one child; Tsunami puts them
straight in the dialog, or in a
<form method=. Every part is
optional.
<button type="button" class="btn" commandfor="dlg-edit" command="show-modal">Edit customer</button>
<dialog class="dialog" id="dlg-edit" aria-labelledby="dlg-edit-title" aria-describedby="dlg-edit-desc" closedby="any">
<header>
<h2 id="dlg-edit-title">Edit customer</h2>
<p id="dlg-edit-desc">Change the details, then save.</p>
<button type="button" class="btn" data-variant="ghost" data-size="icon" commandfor="dlg-edit" command="close" aria-label="Close"><i class="icon icon-x" aria-hidden="true"></i></button>
</header>
<div class="stack">
<div class="field">
<label for="dlg-edit-name">Name</label>
<input id="dlg-edit-name" type="text" value="Emma Wilson" autofocus>
</div>
<div class="field">
<label for="dlg-edit-email">Email</label>
<input id="dlg-edit-email" type="email" value="emma@example.com">
</div>
</div>
<footer>
<button type="button" class="btn" commandfor="dlg-edit" command="close">Cancel</button>
<button type="button" class="btn" data-variant="primary" commandfor="dlg-edit" command="close">Save changes</button>
</footer>
</dialog>
Buttons open and close it with commandfor and command
command= matches Tsugi's
data-, and
command= matches
data-. The button names the
dialog's id in commandfor; the browser calls
showModal( or close(.
Esc closes it too.
<button type="button" class="btn" commandfor="dlg-note" command="show-modal">Order note</button>
<dialog class="dialog" id="dlg-note" data-size="sm" aria-labelledby="dlg-note-title">
<header>
<h2 id="dlg-note-title">Order #110963 note</h2>
<button type="button" class="btn" data-variant="ghost" data-size="icon" commandfor="dlg-note" command="close" aria-label="Close"><i class="icon icon-x" aria-hidden="true"></i></button>
</header>
<div>
<p>Gift wrap, no receipt in the box.</p>
</div>
<footer>
<button type="button" class="btn" commandfor="dlg-note" command="close">Close</button>
</footer>
</dialog>
A click on the backdrop closes it; closedby keeps it open
closedby=: Escape and a click on the
backdrop close it. No closedby: only Escape closes it
(like Tsugi's data-).
closedby=: only a button closes it. A
browser without closedby support ignores it: Escape
still closes the dialog.
<div class="stack" data-direction="row" data-gap="sm">
<button type="button" class="btn" commandfor="dlg-any" command="show-modal">closedby="any"</button>
<button type="button" class="btn" commandfor="dlg-none" command="show-modal">closedby="none"</button>
</div>
<dialog class="dialog" id="dlg-any" data-size="sm" aria-labelledby="dlg-any-title" closedby="any">
<header>
<h2 id="dlg-any-title">Click outside to close</h2>
<p>Escape or a click on the backdrop closes it.</p>
</header>
<footer>
<button type="button" class="btn" commandfor="dlg-any" command="close">Close</button>
</footer>
</dialog>
<dialog class="dialog" id="dlg-none" data-size="sm" aria-labelledby="dlg-none-title" closedby="none">
<header>
<h2 id="dlg-none-title">Importing products</h2>
<p>Only the button closes it.</p>
</header>
<footer>
<button type="button" class="btn" commandfor="dlg-none" command="close">Close</button>
</footer>
</dialog>
Sizes: small, default, large and extra large
data- on the dialog. On a narrow screen
every size is the window less 1rem each side.
<div class="stack" data-direction="row" data-gap="sm">
<button type="button" class="btn" commandfor="dlg-size-sm" command="show-modal">Small</button>
<button type="button" class="btn" commandfor="dlg-size-md" command="show-modal">Default</button>
<button type="button" class="btn" commandfor="dlg-size-lg" command="show-modal">Large</button>
<button type="button" class="btn" commandfor="dlg-size-xl" command="show-modal">Extra large</button>
</div>
<dialog class="dialog" id="dlg-size-sm" data-size="sm" aria-labelledby="dlg-size-sm-title" closedby="any">
<header>
<h2 id="dlg-size-sm-title">Small</h2>
<p>24rem: a short confirmation or one field.</p>
<button type="button" class="btn" data-variant="ghost" data-size="icon" commandfor="dlg-size-sm" command="close" aria-label="Close"><i class="icon icon-x" aria-hidden="true"></i></button>
</header>
<footer>
<button type="button" class="btn" commandfor="dlg-size-sm" command="close">Close</button>
</footer>
</dialog>
<dialog class="dialog" id="dlg-size-md" aria-labelledby="dlg-size-md-title" closedby="any">
<header>
<h2 id="dlg-size-md-title">Default</h2>
<p>32rem: a form.</p>
<button type="button" class="btn" data-variant="ghost" data-size="icon" commandfor="dlg-size-md" command="close" aria-label="Close"><i class="icon icon-x" aria-hidden="true"></i></button>
</header>
<footer>
<button type="button" class="btn" commandfor="dlg-size-md" command="close">Close</button>
</footer>
</dialog>
<dialog class="dialog" id="dlg-size-lg" data-size="lg" aria-labelledby="dlg-size-lg-title" closedby="any">
<header>
<h2 id="dlg-size-lg-title">Large</h2>
<p>42rem: rows of fields, such as an address.</p>
<button type="button" class="btn" data-variant="ghost" data-size="icon" commandfor="dlg-size-lg" command="close" aria-label="Close"><i class="icon icon-x" aria-hidden="true"></i></button>
</header>
<footer>
<button type="button" class="btn" commandfor="dlg-size-lg" command="close">Close</button>
</footer>
</dialog>
<dialog class="dialog" id="dlg-size-xl" data-size="xl" aria-labelledby="dlg-size-xl-title" closedby="any">
<header>
<h2 id="dlg-size-xl-title">Extra large</h2>
<p>56rem: a table or a preview.</p>
<button type="button" class="btn" data-variant="ghost" data-size="icon" commandfor="dlg-size-xl" command="close" aria-label="Close"><i class="icon icon-x" aria-hidden="true"></i></button>
</header>
<footer>
<button type="button" class="btn" commandfor="dlg-size-xl" command="close">Close</button>
</footer>
</dialog>
A form with method="dialog" closes it, and the page reads returnValue
A submit button in a
<form method= closes the dialog
without sending the form, and its value becomes
returnValue. Read it in the close event.
Escape leaves it empty. A close button with
command= and a value sets
it the same way.
Nothing chosen yet.
<div class="stack" data-gap="sm">
<div><button type="button" class="btn" commandfor="dlg-cancel" command="show-modal">Cancel order</button></div>
<p id="dlg-cancel-result">Nothing chosen yet.</p>
</div>
<dialog class="dialog" id="dlg-cancel" data-size="sm" aria-labelledby="dlg-cancel-title">
<form method="dialog">
<header>
<h2 id="dlg-cancel-title">Cancel order #110963?</h2>
<p>The customer is refunded and the items go back into stock.</p>
</header>
<footer>
<button class="btn" value="keep">Keep order</button>
<button class="btn" data-variant="danger" value="cancel">Cancel order</button>
</footer>
</form>
</dialog>
<script type="module">
const dialog = document.getElementById("dlg-cancel");
dialog.addEventListener("close", () => {
document.getElementById("dlg-cancel-result").textContent =
`returnValue = "${dialog.returnValue}"`;
dialog.returnValue = "";
});
</script>
A long section scrolls inside, and the header and footer stay
The panel is never taller than the window; the middle
<div> scrolls on its own.
<button type="button" class="btn" commandfor="dlg-items" command="show-modal">Order items</button>
<dialog class="dialog" id="dlg-items" aria-labelledby="dlg-items-title" closedby="any">
<header>
<h2 id="dlg-items-title">Order items</h2>
<p>The 24 lines of order #110963.</p>
<button type="button" class="btn" data-variant="ghost" data-size="icon" commandfor="dlg-items" command="close" aria-label="Close"><i class="icon icon-x" aria-hidden="true"></i></button>
</header>
<div>
<table class="table">
<thead>
<tr><th scope="col">Product</th><th scope="col">SKU</th><th scope="col" data-align="end">Qty</th></tr>
</thead>
<tbody>
<tr><td>Bollard Lamp - Black</td><td>LAMP-BLK</td><td data-align="end">2</td></tr>
<tr><td>Magic Sheets - Twin</td><td>SHEET-TWIN</td><td data-align="end">1</td></tr>
<tr><td>Timeless Watch</td><td>WATCH-01</td><td data-align="end">1</td></tr>
<tr><td>Magic Pen</td><td>PEN-01</td><td data-align="end">4</td></tr>
<tr><td>Canvas Tote</td><td>TOTE-01</td><td data-align="end">1</td></tr>
<tr><td>Desk Mat</td><td>MAT-01</td><td data-align="end">2</td></tr>
<tr><td>Ceramic Mug</td><td>MUG-01</td><td data-align="end">6</td></tr>
<tr><td>Notebook Set</td><td>NOTE-03</td><td data-align="end">3</td></tr>
<tr><td>Wool Throw</td><td>THROW-01</td><td data-align="end">1</td></tr>
<tr><td>Brass Hook</td><td>HOOK-02</td><td data-align="end">8</td></tr>
<tr><td>Linen Apron</td><td>APRON-01</td><td data-align="end">1</td></tr>
<tr><td>Oak Tray</td><td>TRAY-01</td><td data-align="end">2</td></tr>
<tr><td>Bollard Lamp - White</td><td>LAMP-WHT</td><td data-align="end">2</td></tr>
<tr><td>Magic Sheets - Queen</td><td>SHEET-QUEEN</td><td data-align="end">1</td></tr>
<tr><td>Timeless Watch - Gold</td><td>WATCH-02</td><td data-align="end">1</td></tr>
<tr><td>Magic Pen - Red</td><td>PEN-02</td><td data-align="end">4</td></tr>
<tr><td>Canvas Tote - Large</td><td>TOTE-02</td><td data-align="end">1</td></tr>
<tr><td>Desk Mat - Grey</td><td>MAT-02</td><td data-align="end">2</td></tr>
<tr><td>Ceramic Mug - Blue</td><td>MUG-02</td><td data-align="end">6</td></tr>
<tr><td>Notebook Set - Lined</td><td>NOTE-05</td><td data-align="end">3</td></tr>
<tr><td>Wool Throw - Grey</td><td>THROW-02</td><td data-align="end">1</td></tr>
<tr><td>Brass Hook - Large</td><td>HOOK-04</td><td data-align="end">8</td></tr>
<tr><td>Linen Apron - Navy</td><td>APRON-02</td><td data-align="end">1</td></tr>
<tr><td>Oak Tray - Round</td><td>TRAY-02</td><td data-align="end">2</td></tr>
</tbody>
</table>
</div>
<footer>
<button type="button" class="btn" commandfor="dlg-items" command="close">Close</button>
</footer>
</dialog>
An alert dialog asks a question the user has to answer
role= on
.: no dividers and no close button. Use
it to confirm something that cannot be undone. Leave
closedby out so a stray click on the backdrop does
not answer; Escape still cancels.
<button type="button" class="btn" commandfor="dlg-alert" command="show-modal">Delete customer</button>
<dialog class="dialog" id="dlg-alert" role="alertdialog" aria-labelledby="dlg-alert-title" aria-describedby="dlg-alert-desc">
<header>
<h2 id="dlg-alert-title">Delete this customer?</h2>
<p id="dlg-alert-desc">Their orders stay, but they can no longer sign in. This cannot be undone.</p>
</header>
<footer>
<button type="button" class="btn" commandfor="dlg-alert" command="close">Cancel</button>
<button type="button" class="btn" data-variant="danger" commandfor="dlg-alert" command="close">Delete</button>
</footer>
</dialog>
A figure in the header puts an icon beside the question
data- on <figure>:
ok, danger, warning,
info; without it the figure is gray. It works in any
dialog header.
<button type="button" class="btn" commandfor="dlg-icon" command="show-modal">Delete product</button>
<dialog class="dialog" id="dlg-icon" role="alertdialog" aria-labelledby="dlg-icon-title" aria-describedby="dlg-icon-desc">
<header>
<figure data-variant="danger"><i class="icon icon-trash-2" aria-hidden="true"></i></figure>
<h2 id="dlg-icon-title">Delete Timeless Watch?</h2>
<p id="dlg-icon-desc">The product and its 3 variants are removed. Orders that hold it keep their lines.</p>
</header>
<footer>
<button type="button" class="btn" commandfor="dlg-icon" command="close">Cancel</button>
<button type="button" class="btn" data-variant="danger" commandfor="dlg-icon" command="close">Delete product</button>
</footer>
</dialog>
data-size="sm" centres an alert dialog and gives the two buttons equal width
<button type="button" class="btn" commandfor="dlg-signout" command="show-modal">Sign out</button>
<dialog class="dialog" id="dlg-signout" role="alertdialog" data-size="sm" aria-labelledby="dlg-signout-title" aria-describedby="dlg-signout-desc">
<header>
<figure><i class="icon icon-log-out" aria-hidden="true"></i></figure>
<h2 id="dlg-signout-title">Sign out?</h2>
<p id="dlg-signout-desc">Unsaved changes on this page are lost.</p>
</header>
<footer>
<button type="button" class="btn" commandfor="dlg-signout" command="close">Cancel</button>
<button type="button" class="btn" data-variant="primary" commandfor="dlg-signout" command="close">Sign out</button>
</footer>
</dialog>
API
Methods and properties (native)
| Member | Type | Description |
|---|---|---|
showModal(
|
method |
Opens the dialog over the dimmed page; what
command= calls.
|
close(
|
method |
Closes it; the optional string becomes
returnValue. What
command= calls.
|
requestClose(
|
method |
Like Escape: fires cancel first, which can
keep it open.
command=.
|
returnValue |
property |
The value from close( or from
the value of the button that submits a
method= form. Not reset on
open: clear it yourself.
|
open |
property |
true while open. Do not set the
open attribute by hand: that dialog is not
modal.
|
Events (native)
| Event | Detail | Description |
|---|---|---|
close |
none |
After the dialog has closed, however it closed. Read
returnValue here.
|
cancel |
none |
Before Escape (or a backdrop click) closes it;
preventDefault( keeps it open.
|
Keyboard
| Key | Description |
|---|---|
| Esc |
Closes the dialog, unless
closedby=; a popover open inside
it closes first.
|
| Tab | Moves between the controls of the dialog only. |
| Enter |
In a field: submits the form, which closes the dialog when
it has method=.
|
Everything here is the browser's own dialog element; Tsunami adds only styles.
Attributes
| Attribute / element | Values | Description |
|---|---|---|
class=
|
on <dialog> |
The component. |
data-
|
sm (24rem), none (default, 32rem),
lg (42rem), xl (56rem)
|
Width; never wider than the window less 1rem each side. |
role=
|
on the dialog |
A question with answers; with
data- it is centred
and its two buttons are equal width.
|
aria-
|
id of the <h2> |
Names the dialog. Required. |
aria-
|
id of the <p> |
Reads the line under the title as the description. |
closedby |
any, closerequest (default for a
modal), none
|
any: Escape and a backdrop click;
closerequest: Escape only; none:
buttons only.
|
commandfor + command |
dialog id + show-,
close, request-
|
On a button: opens or closes the dialog it names. |
value |
text, on a close or submit button |
Becomes the dialog's
returnValue.
|
autofocus |
on a control in the dialog | The control that gets focus on open. Without it the first focusable control does. |
> form[
|
optional wrapper | Wraps the parts; a submit closes the dialog. |
> header |
h2, p, close
button, figure
|
The close button sits at the end of the first row. |
> header > figure |
data-: none (gray, default),
ok, danger,
warning, info
|
A round icon beside the title. |
> div |
any content | The body; scrolls when it is long. |
> footer |
buttons | Buttons at the end, main action last. |