Skip to content

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.

Confirm something

Body content.

PartPurpose
.hc-dialogRoot <dialog> element.
.hc-dialog__headerTitle row, with a bottom divider.
.hc-dialog__titleHeading inside the header.
.hc-dialog__bodyMain content area.
.hc-dialog__footerAction row, right-aligned, top divider.

The parts are conventional — you can omit any of them.

Use the native methods:

dialog.showModal(); // modal, with focus trap and ::backdrop
dialog.show(); // non-modal, no backdrop
dialog.close(); // close (modal or non-modal)
dialog.close('confirm'); // close and set returnValue

Listen for the close event to react to dismissal:

dialog.addEventListener('close', () => {
if (dialog.returnValue === 'confirm') {
// user accepted
}
});

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.

  • Use the native <dialog> element. Do not simulate a dialog with a <div role="dialog"> unless you genuinely cannot use showModal().
  • The browser handles focus trap and Escape automatically when the dialog is opened via showModal().
  • Label the dialog. Either:
    • Set aria-labelledby on the dialog pointing at the title id, or
    • Place the title before any focusable element so it is read on open.
  • For destructive confirms, focus the Cancel button on open — never the destructive action.
Token pathPurpose
dialog.bg / -fgSurface colors.
dialog.borderBorder around the dialog.
dialog.radiusBorder radius.
dialog.paddingPadding for each part.
dialog.gapGap between footer buttons.
dialog.max-widthMaximum inline size.
dialog.backdrop::backdrop color.
Show the generated CSS variables
--hc-dialog-bg | -fg | -border
--hc-dialog-radius | -padding | -gap | -max-width
--hc-dialog-backdrop