<drupal-dialog> custom element demo<drupal-dialog> is a custom element (~30 lines,
no dependencies) that owns showModal(), closedby polyfill, and
self-removal on close. The server delivers two <hx-partial> tags per
response — delete the old slot by id, insert the new one before end of body. No htmx
extension, no init code, no container div. Trigger buttons carry only hx-action.
#drupal-modal (modal) and
#drupal-off-canvas (non-modal) are independent — both can be open
simultaneously. Custom slots use any id.
closedby="any"Native Chrome 133+, Firefox 136+. <drupal-dialog> polyfills for Safari.
Open the tray, click a block, then submit the form empty. The server returns
the same <drupal-dialog id="drupal-off-canvas"> with validation
errors via hx-target="#drupal-off-canvas" hx-swap="outerMorph".
The tray stays open, focus returns to the invalid field, scroll position is
preserved. No delete+insert — morph updates the existing element in place.
The tray uses #drupal-off-canvas. Clicking a block inside sends a new
request that delete+inserts the same slot — the tray content is replaced but it
stays open. No persist needed. Open a modal on top to see both slots coexist.
#drupal-off-canvas slotIndependent slot from #drupal-modal. Open both simultaneously to see
Layout Builder-style coexistence.
data-persist on <drupal-dialog> — close hides it,
DOM is kept. Two variants: inserted open (opens immediately), inserted closed
(stays hidden until commandfor triggers it).
Note: commandfor targets the inner <dialog id="persist-dialog-inner">,
not the <drupal-dialog id="persist-dialog"> wrapper — two ids are required.
Open a non-modal first so the page stays interactive. The second response targets
the same #drupal-modal slot — delete partial removes it, insert partial
appends a modal replacement. connectedCallback calls
showModal() on the new one.
Open a non-modal first so the page stays interactive. Server response contains only
<hx-partial hx-target="#drupal-modal" hx-swap="delete">.
disconnectedCallback fires on the custom element for cleanup.
Or return a new closed dialog — stays in DOM, commandfor reopens it.
Note: commandfor targets the inner <dialog id="drupal-modal-inner">,
not the <drupal-dialog id="drupal-modal"> wrapper — two ids are required.
role="alertdialog"