Browse documentation
On this page
Modal
The native <dialog> element is styled as a centered modal with a
backdrop. Opening it, closing it, locking the page behind it and animating
it in are all the platform's work — Cirth supplies the appearance and
nothing else, and no longer asks your script for anything.
Show HTML
<!-- In real usage, open with dialog.showModal() from a button's onclick.
Position is forced static here only so the preview stays inline. -->
<dialog open aria-labelledby="demo-confirm-title" style="position: static; width: 100%; min-width: 0; height: auto; min-height: 0; backdrop-filter: none; background: transparent;">
<article>
<header>
<button type="button" aria-label="Close" rel="prev" style="float: right;"></button>
<strong id="demo-confirm-title">Confirm action</strong>
</header>
<p>Are you sure you want to delete this item? This cannot be undone.</p>
<footer>
<button type="button" class="secondary">Cancel</button>
<button type="button">Confirm</button>
</footer>
</article>
</dialog>
<dialog id="confirm" aria-labelledby="confirm-title">
<article>
<header>
<button type="button" aria-label="Close" rel="prev"></button>
<strong id="confirm-title">Confirm action</strong>
</header>
<p>Are you sure you want to delete this item? This cannot be undone.</p>
<footer>
<button type="button" class="secondary">Cancel</button>
<button type="button">Confirm</button>
</footer>
</article>
</dialog>
Opening and closing #
Two attributes, no script:
<button type="button" commandfor="confirm" command="show-modal">
Delete item
</button>
<dialog id="confirm" aria-labelledby="confirm-title">
<article>
<h2 id="confirm-title">Confirm action</h2>
<p>Are you sure?</p>
<footer>
<button type="button" commandfor="confirm" command="request-close">
Cancel
</button>
<form method="dialog"><button type="submit">Confirm</button></form>
</footer>
</article>
</dialog>
command="show-modal" opens it, close and request-close close it — the
second firing a cancel event first, so a form can object. A
<form method="dialog"> closes the dialog on submit and reports which
button did it through dialog.returnValue.
Invoker commands are newer than Cirth's browser floor (Chrome 135, Firefox 144, Safari 26.2). Below that the attributes are ignored and the button does nothing, so treat them as an enhancement and keep the DOM API wherever the interaction is essential:
const dialog = document.getElementById("confirm");
dialog.showModal(); // open
dialog.close(); // close
That is a compatibility path and application logic — confirming a deletion, sending a request — not something Cirth needs in order to present the component.
Behavior #
dialogfills the viewport (position: fixed, full width/height) and centers its content; the backdrop uses--cirth-modal-overlay-background-colorand--cirth-modal-overlay-backdrop-filter(a blur by default).- The
> articleis the actual modal card. Its width ismin(100% - 2 * --cirth-spacing, --cirth-modal-max-width)(the cap defaults to43.75rem/700px), so it follows the available space continuously with no viewport breakpoint. It is scrollable if content is taller than the viewport. - A close control,
.closeor:is(a, button)[rel="prev"]in the header, is styled as a small floated icon button (--cirth-icon-close). - The page stops scrolling on its own.
html:has(dialog[open])setsoverflow: hidden, and the scrollbar's gutter is held open permanently (scrollbar-gutter: stableon the document, see Document) so hiding the overflow moves nothing. This replaces the old.modal-is-openclass and the--cirth-scrollbar-widthmeasurement it needed from your script. Scoped builds do not do this: they are anchored inside a wrapper and have no business reaching the document root, so a scoped widget has to ask its host to lock the page. - It animates itself in.
@starting-stylegives the dialog the "before" frame an element entering the top layer otherwise cannot have: the backdrop fades and the card slides down. This replaces.modal-is-opening. - The close is instant outside Chromium. Animating an element out of
the top layer needs the
overlayproperty, which only Chromium ships, sodisplayandoverlaytransition withallow-discretewhere they can and the dialog simply disappears where they cannot. Nothing is lost by that: closing means the dialog goes away. - All of the motion collapses under reduced motion.