Dialog
A modal built on the native <dialog> element. The browser provides the top layer, inert background and Escape; iVOLT adds focus placement, focus return, backdrop closing, declarative triggers and cancelable events.
<a class="iv-button iv-button--primary" href="#signup" data-iv-open="signup">Create account</a>
<dialog class="iv-dialog" id="signup" data-iv-component="dialog" data-iv-initial-focus="#signup-email" aria-labelledby="signup-title">
<form method="dialog" class="iv-dialog__panel">
<header class="iv-dialog__header">
<h2 class="iv-dialog__title" id="signup-title">Create account</h2>
<button class="iv-button iv-button--ghost iv-button--icon" type="button" data-iv-close aria-label="Close">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg>
</button>
</header>
<div class="iv-dialog__body iv-stack">
<p class="iv-u-m-0">Demo dialog. Nothing is sent anywhere.</p>
<div class="iv-field">
<label class="iv-label" for="signup-email">Work email</label>
<input class="iv-input" id="signup-email" type="email" autocomplete="email">
</div>
</div>
<footer class="iv-dialog__footer">
<a class="iv-button iv-button--ghost" href="#" data-iv-close>Cancel</a>
<button class="iv-button iv-button--primary" value="confirm">Continue</button>
</footer>
</form>
</dialog>// data-iv-component="dialog"
{
open: "signup",
initialFocus: "#signup-email",
close: true,
}Markup
Give the dialog data-iv-component="dialog", an id and an accessible name (aria-labelledby). Triggers use data-iv-open="id"; anything inside with data-iv-close closes it. A <form method="dialog"> makes buttons close the dialog with their value as return value.
JavaScript
import { Dialog } from "@intervolutions/ivolt/dialog";
const dialog = Dialog.getOrCreate(document.getElementById("signup"), { closeOnBackdrop: false });
dialog.open({ trigger: button });
dialog.element.addEventListener("iv:close", (e) => { if (e.detail.reason === "escape" && dirty) e.preventDefault(); });Options (defaults < data-iv-* < JS)
| Option | Attribute | Default | Effect |
|---|---|---|---|
closeOnBackdrop | data-iv-close-on-backdrop | true | Click outside the panel closes. |
closeOnEscape | data-iv-close-on-escape | true | Escape closes (the native cancel is always intercepted so the event stays cancelable). |
initialFocus | data-iv-initial-focus | null | Selector to focus on open; otherwise the first focusable element, else the dialog. |
returnFocus | data-iv-return-focus | true | Focus goes back to the trigger on close. |
Methods and events
open({ trigger }), close(reason, returnValue), toggle(), destroy(), isOpen, returnValue. Events bubble from the dialog element: iv:open and iv:close are cancelable; iv:opened and iv:closed follow. detail.reason is one of trigger, escape, backdrop, form, api, external.
Keyboard
Tab and Shift+Tab stay inside the modal (browser behaviour of showModal); Escape closes; Enter in the form submits and closes. Focus returns to the trigger.
Without JavaScript
The trigger is a link to the dialog's id. With JavaScript the link is intercepted and the URL does not change. Without it, .iv-dialog:target shows the dialog as a static block with its close link. Keep essential content reachable elsewhere too; a dialog is not a page.
Common mistakes
- Calling
dialog.close()on the element directly: it works, butiv:closecannot cancel it and the reason isexternal. - Trigger inside the dialog pointing to itself.
- No accessible name (
aria-labelledbyoraria-label).