Dialog
hc-dialog is a thin class applied to the standard <dialog>
element. The browser owns the modal lifecycle — focus trap,
Escape-to-close, inert background, top-layer rendering — and the
component adds only the visual surface.
Basic HTML
Section titled “Basic HTML”<button class="hc-button" data-variant="primary" onclick="document.getElementById('my-dialog').showModal()"> Open dialog</button>
<dialog class="hc-dialog" id="my-dialog" aria-labelledby="my-dialog-title"> <header class="hc-dialog__header"> <h2 class="hc-dialog__title" id="my-dialog-title">Confirm something</h2> </header> <div class="hc-dialog__body"> <p>Body content.</p> </div> <footer class="hc-dialog__footer"> <!-- form method="dialog" closes the dialog natively, no JS needed --> <form method="dialog"><button class="hc-button">Cancel</button></form> <form method="dialog"><button class="hc-button" data-variant="primary">OK</button></form> </footer></dialog>| Part | Purpose |
|---|---|
.hc-dialog | Root <dialog> element. |
.hc-dialog__header | Title row, with a bottom divider. |
.hc-dialog__title | Heading inside the header. |
.hc-dialog__body | Main content area. |
.hc-dialog__footer | Action row, right-aligned, top divider. |
The parts are conventional — you can omit any of them.
Open and close
Section titled “Open and close”Use the native methods:
dialog.showModal(); // modal, with focus trap and ::backdropdialog.show(); // non-modal, no backdropdialog.close(); // close (modal or non-modal)dialog.close('confirm'); // close and set returnValueListen for the close event to react to dismissal:
dialog.addEventListener('close', () => { if (dialog.returnValue === 'confirm') { // user accepted }});htmx usage
Section titled “htmx usage”For modals whose body comes from the server, see the Remote dialog recipe. For a pre-flight confirmation step (no round trip until accepted), see Confirm action.
For a form inside a dialog that should close on success, opt in with
data-hc-close-dialog-on-success:
<form class="hc-form" data-hx-post="/items" data-hx-target="closest dialog" data-hx-swap="outerHTML" data-hc-close-dialog-on-success> …</form>The behavior listens for htmx:afterRequest, checks for success,
and calls dialog.close() on the closest <dialog> ancestor.
Accessibility
Section titled “Accessibility”- Use the native
<dialog>element. Do not simulate a dialog with a<div role="dialog">unless you genuinely cannot useshowModal(). - The browser handles focus trap and Escape automatically when the
dialog is opened via
showModal(). - Label the dialog. Either:
- Set
aria-labelledbyon the dialog pointing at the title id, or - Place the title before any focusable element so it is read on open.
- Set
- For destructive confirms, focus the Cancel button on open — never the destructive action.
Theming tokens
Section titled “Theming tokens”| Token path | Purpose |
|---|---|
dialog.bg / -fg | Surface colors. |
dialog.border | Border around the dialog. |
dialog.radius | Border radius. |
dialog.padding | Padding for each part. |
dialog.gap | Gap between footer buttons. |
dialog.max-width | Maximum inline size. |
dialog.backdrop | ::backdrop color. |
CSS variables
Section titled “CSS variables”Show the generated CSS variables
--hc-dialog-bg | -fg | -border--hc-dialog-radius | -padding | -gap | -max-width--hc-dialog-backdropRelated
Section titled “Related”- Popover component — non-modal sibling.
- Confirm action recipe
- Remote dialog recipe