TLDR
DtModalnow renders a native<dialog>element instead of a<div role="dialog">.- The native
::backdroppseudo-element replaces the custom.d-modal__backdropdiv. - Focus stays trapped within the dialog and Escape closes it — handled by the native
<dialog>together with thev-dt-focustrapdirective (Escape via the nativecancelevent). - Popovers and tooltips inside modals now auto-append to the nearest
<dialog>to stay in the browser's top layer. - We are not considering this a breaking change since consumers should not be targeting internal DOM structure. This guide is provided in case you are.
What Changed
DOM structure
The root element of DtModal changed from:
<!-- Before -->
<div class="d-modal" role="dialog" aria-hidden="false">
<div class="d-modal__dialog">...</div>
</div>
to:
<!-- After -->
<dialog class="d-modal" open>
<div class="d-modal__dialog">...</div>
</dialog>
The role="dialog" attribute is no longer needed — the <dialog> element has implicit dialog semantics. Visibility is now controlled via the [open] attribute instead of [aria-hidden].
Backdrop
Previously, the backdrop was a custom <div> (.d-modal__backdrop or the .d-modal overlay itself with aria-hidden). Now the native ::backdrop pseudo-element fires when showModal() is called, but Dialtone makes it transparent — the .d-modal element itself still serves as the visual backdrop overlay.
If you were targeting ::backdrop in your CSS, your styles may now conflict with the transparent reset:
/* Dialtone sets this internally — your overrides may no longer apply as expected */
:where(dialog).d-modal::backdrop {
background: transparent;
}
CSS selectors
The CSS was updated to support both the legacy [aria-hidden='false'] selector and the native [open] attribute:
/* Before */
.d-modal[aria-hidden='false'] { ... }
/* After */
:is(.d-modal, .d-modal--transparent):is([aria-hidden='false'], [open]) { ... }
If you targeted .d-modal[aria-hidden='false'] in your CSS, those selectors still work but you should migrate to targeting [open] or remove the attribute selector entirely.
Focus management
- Focus trapping — while the modal is open, keyboard focus stays inside the dialog: Tab from the last focusable element wraps to the first, and Shift+Tab from the first wraps to the last. This is enforced by the native
<dialog>(opened withshowModal()) together with thev-dt-focustrapdirective. - Escape key — pressing Escape closes the modal: the native
<dialog>emits acancelevent, whichDtModalhandles (@cancel.prevent) to close it. - Initial focus still works via the
initialFocusElementprop ('first','#id', or anHTMLElement).
Transition events
DtLazyShow is no longer used to wrap the dialog. If you were listening for transition events from the lazy-show wrapper, those events no longer fire. The modal now uses internal onAfterEnter / onAfterLeave callbacks tied to the dialog lifecycle.
Popovers and tooltips inside modals
DtPopover and DtTooltip now auto-detect when they are inside a native <dialog> and append themselves to it instead of <body>. This keeps them in the browser's top layer so they render correctly above the modal backdrop.
- The
appendTo="body"default now resolves to the nearest ancestor<dialog>when inside one. - To force appending to
<body>regardless of dialog context, passdocument.bodyas anHTMLElementinstead of the string'body'.
Do I Need to Do Anything?
Most likely no. These are internal implementation changes. You are affected only if you:
- Target
.d-modal__backdropor other internal class selectors in your CSS — review and update those selectors. - Apply
::backdroppseudo-element styles — the native backdrop is now transparent; the.d-modalelement is the visual overlay. - Had custom
focusinorkeydown.tabhandlers wired around the old DOM structure — focus trapping is now handled by the native<dialog>together with thev-dt-focustrapdirective, and Escape via the nativecancelevent. - Relied on
DtLazyShowtransition events wrapping the dialog — those no longer fire. - Relied on
appendTo="body"for popovers/tooltips inside modals to append to<body>— they now append to the<dialog>. Passdocument.bodyexplicitly if you need the old behavior.
If none of the above apply, no action is required.
Need Help?
Reach out in the #dialtone Dialpad channel with any questions or issues.