DtModal now uses a native <dialog> element. This changes the DOM structure, backdrop behavior, and focus management. Popovers and tooltips inside modals also auto-append to the dialog.
DtModal now renders a native <dialog> element instead of a <div role="dialog">.::backdrop pseudo-element replaces the custom .d-modal__backdrop div.v-dt-focustrap directive and DtModal's own key handling.<dialog>.modal prop controls this and defaults to false, so surfaces such as toasts and notifications can render above a modal again via the z-index scale. See Top layer and the modal prop.modal default is a behaviour change, not just an internal one: modals that previously entered the top layer no longer do unless you pass modal. Nothing needs to change for most consumers, but see Do I Need to Do Anything? for who is affected.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].
Previously, the backdrop was a custom <div> (.d-modal__backdrop or the .d-modal overlay itself with aria-hidden). The native ::backdrop pseudo-element only renders for a dialog in the top layer, so it applies solely when modal is true — and Dialtone makes it transparent in any case. The .d-modal element itself is the visual backdrop overlay, painted by its own background-color, which is why the modal looks identical in both modes.
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;
}
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.
v-dt-focustrap directive, independently of modal.modal is set and not otherwise. Without it the page outside the dialog stays reachable, which is deliberate: see Top layer and the modal prop.cancel event only fires for top-layer dialogs, so it is used when modal is true and a keydown handler covers the default case.initialFocusElement prop ('first', '#id', or an HTMLElement).modal prop #Opening a <dialog> with showModal() promotes it to the browser's top layer, which paints above the entire normal stacking order. z-index does not apply across that boundary, so nothing outside the top layer can render above such a dialog no matter how high its z-index is.
That broke Dialtone's own published contract: --zi-notification (700) is defined above --zi-modal (600), but the comparison never happened, so toasts, banners and application notification surfaces all rendered behind any open modal.
DtModal therefore takes a modal prop, defaulting to false:
<!-- Default: stays in the normal stacking order, z-index scale applies -->
<dt-modal :open="open" header-text="Settings" />
<!-- Opt in to the top layer for a genuinely blocking dialog -->
<dt-modal :open="open" modal header-text="Confirm deletion" />
Dim overlay, positioning, focus trap and scroll lock are DtModal's own work regardless of the prop — focus trapping comes from v-dt-focustrap, scroll locking from disableRootScrolling(), and the dim from .d-modal's own background — so the modal looks and behaves the same either way. Two things do differ:
aria-modal is not set. Only the top layer makes the rest of the page inert, so with modal unset it stays reachable — and aria-modal="true" is omitted to match, since claiming it would tell assistive technology to ignore the very overlays this mode exists to serve. This is the point of the mode, not an oversight: an application overlay rendered above a modal has to be usable, and a call notification you can see but cannot answer is worse than one hidden behind the modal. Keyboard focus is still contained by the focus trap; what remains reachable is pointer and assistive-technology access to the rest of the page. Pass modal if you need the page fully sealed — you then get the browser's inertness and aria-modal="true" together.keydown rather than the native cancel event, and is skipped when a nested widget has already called preventDefault() on it, so a dropdown inside the modal closes itself first without taking the modal with it.Use modal when the dialog must out-rank another top-layer element — a <dialog> opened by a third-party library, or a fullscreen element — or when the rest of the page must genuinely be inert while it is open.
Leave it off when your application has overlays of its own — toasts, call notifications, update banners — that need to reach the user while a modal is open.
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.
DtPopover and DtTooltip now auto-detect when they are inside a native <dialog> and append themselves to it instead of <body>. This keeps them rendering above the modal backdrop rather than behind it.
appendTo="body" default now resolves to the nearest ancestor <dialog> when inside one.<body> regardless of dialog context, pass document.body as an HTMLElement instead of the string 'body'.Most likely no. These are internal implementation changes. You are affected only if you:
.d-modal__backdrop or other internal class selectors in your CSS — review and update those selectors.::backdrop pseudo-element styles — the native backdrop is now transparent; the .d-modal element is the visual overlay.focusin or keydown.tab handlers wired around the old DOM structure — focus trapping is now handled by the v-dt-focustrap directive. Escape is handled on keydown by default, and via the native cancel event only when modal is set.DtLazyShow transition events wrapping the dialog — those no longer fire.appendTo="body" for popovers/tooltips inside modals to append to <body> — they now append to the <dialog>. Pass document.body explicitly if you need the old behavior.<dialog> from a third-party library, or a fullscreen element. Pass modal to opt back into the top layer.If none of the above apply, no action is required.
Reach out in the #dialtone Dialpad channel with any questions or issues.
DtModal Native Dialog Migration documentation last updated Friday, September 4, 2026
fix/popover-modal-zindex-scope