Behaviors — Dialog
and-dialog-trigger="target-id" opens whatever element has that id as a
modal. The target can be a <template> (cloned fresh on every open — no need to
hide it in the page beforehand) or a real, already-visible element (reparented
into the dialog and moved back on close):
<button and-dialog-trigger="settings-panel">Open settings</button>
<template id="settings-panel" and-dialog-position="right" and-dialog-width="20rem"> <div> <strong>Settings</strong> <button and-dialog-close>Close</button> </div></template>If your dialog’s content is already live in the page rather than a <template>,
hide it with class="hidden" (a Tailwind-style utility, not
style="display: none") — createDialog specifically detects and clears that
class when it moves the element in, and re-adds it when the element moves back
on close. An inline style="display: none" is only cleared on open, never
restored on close, so the element would be left visible in its original spot in
the page once the dialog is dismissed.
Attributes
Section titled “Attributes”| Attribute | Where | Values | Default |
|---|---|---|---|
and-dialog-trigger | trigger | string (target element id) | required |
and-dialog-position | trigger and/or target | 'center'|'top'|'bottom'|'left'|'right' | 'center' |
and-dialog-backdrop | trigger and/or target | 'false' to disable | shown |
and-dialog-close-on-backdrop | trigger and/or target | 'false' to disable | enabled |
and-dialog-close-on-escape | trigger and/or target | 'false' to disable | enabled |
and-dialog-width / -height | trigger and/or target | any CSS length | — |
and-dialog-panel-class | trigger and/or target | class name(s) | — |
and-dialog-backdrop-class | trigger and/or target | class name(s) | — |
and-dialog-close | any descendant | — | — |
Any attribute is read from the trigger first, then the target as a fallback
— set it on whichever element is more convenient. Any element with
and-dialog-close inside the opened content closes the dialog when clicked.
Imperative API
Section titled “Imperative API”import { createDialog } from '@andersseen/behaviors/dialog';
const dialog = createDialog(document.getElementById('panel'), { position: 'right',});dialog.closed.then(() => console.log('dialog dismissed'));// dialog.close();content accepts three shapes, each handled differently:
| Type | Behavior |
|---|---|
HTMLElement | Reparented into the dialog panel (a comment placeholder marks its original spot) and moved back on close — including restoring a hidden class if it had one. |
HTMLTemplateElement | Cloned via importNode into the panel — the original template is left untouched, so you can open the same template more than once. |
string | Set as panelEl.innerHTML directly. |
| Option | Type | Default |
|---|---|---|
position | 'center'|'top'|'bottom'|'left'|'right' | 'center' |
backdrop | boolean | true |
closeOnBackdropClick | boolean | true |
closeOnEscape | boolean | true |
width / height | string (any CSS length) | — |
panelClass | string | string[] | — |
backdropClass | string | string[] | — |
DialogRef is { close(): void; readonly closed: Promise<void> } — closed
resolves once, whenever the dialog is dismissed (backdrop click, Escape, a
[and-dialog-close] element inside the content, or your own close() call —
[and-dialog-close] auto-wiring works the same whether the dialog was opened
declaratively or imperatively).
Dialogs stack: open a second one while the first is open and Escape only
closes the top-most. Body scroll is locked (overflow: hidden) while at
least one dialog is open, and restored only once the last one closes. Focus
moves to the first focusable element in the panel on open, is trapped inside it
on Tab/Shift+Tab, and returns to whatever had focus before createDialog
was called once it closes.
This can't be undone.
<button id="trigger">Delete item…</button>
<template id="confirm-template"> <div> <strong>Delete this item?</strong> <button and-dialog-close>Cancel</button> <button id="confirm-ok">Delete</button> </div></template>
<script type="module"> import { createDialog } from '@andersseen/behaviors/dialog';
document.getElementById('trigger').addEventListener('click', () => { const dialog = createDialog(document.getElementById('confirm-template')); document .getElementById('confirm-ok') .addEventListener('click', () => dialog.close()); });</script>Accessibility
Section titled “Accessibility”Sets role="dialog" and aria-modal="true" on the panel, traps
Tab/Shift+Tab inside it (disabled/hidden controls skipped by the
focusable-elements query), locks body scroll while open, and restores focus to
whatever triggered the dialog once it closes.