嵌套调用showmodal()必然失败,因浏览器强制单顶层限制;必须并列置于body下,safari对父容器和css属性敏感,多层模态需复用单dialog或手动实现焦点/遮罩/可访问性逻辑。

dialog 嵌套调用 showModal() 必然失败
浏览器规范强制限制:任意时刻仅允许一个元素处于顶层(top layer),showModal() 的作用就是把 <dialog></dialog> 推入该层并锁定交互。第二次调用会直接抛出错误:DOMException: Failed to execute 'showModal' on 'HTMLDialogElement': The element is already in a top layer.
这不是兼容性问题,也不是 CSS z-index 能绕过的——::backdrop 是伪元素,没有 DOM 节点,无法参与层级堆叠。
- 即使 DOM 结构上写了
<dialog><dialog></dialog></dialog>,内层<dialog></dialog>完全不会响应任何方法 -
show()也不行:它不进顶层,背景仍可点击、Tab 仍能切出去,无法实现“模态中再模态”的阻断 - 别在
close()回调里立刻调用下一个showModal()——必须等事件循环清空顶层状态,稳妥做法是setTimeout(() => nextDialog.showModal(), 0)
dialog 必须是 body 的直接子元素
否则 Safari(v17.6 及更早)根本不会渲染 ::backdrop,遮罩消失、Esc 键失效、点击空白处无响应——这不是样式没写对,是浏览器解析规则硬性要求。
常见踩坑位置:
- 嵌套在
<div id="app"> 或 <code><main></main>里 - 外层容器设置了
transform、will-change、overflow: hidden - 放在
<form></form>或<section></section>内部,导致居中逻辑错乱
正确写法只能是:
<dialog id="main-dialog">...</dialog><dialog id="confirm-dialog">...</dialog><!-- 所有 dialog 并列放在 body 最外层 -->
想实现“多层效果”,只有两种安全路径
所谓“嵌套”,实际要的是视觉分层 + 逻辑可退栈,不是 DOM 嵌套。强行手写多层 position: fixed 弹窗,代价远超预期:
- 焦点必须手动 trap:监听
keydown拦截 Tab/Shift+Tab,在弹窗内循环 - 遮罩点击判定要严格:
if (e.target === overlayEl) closeDialog(),不能靠冒泡或 class 判断 - 移动端必须用
pointerdown,iOS 上click有 300ms 延迟,关闭卡顿 - 每次打开/关闭都要同步
document.body.inert、aria-hidden和焦点恢复,漏一项就破坏屏幕阅读器体验
更轻量的替代方案是复用单个 <dialog></dialog>:用 JS 替换内部 HTML,维护一个状态栈(如 history.push() + popstate),每次切换后手动调用 dialog.querySelector('[autofocus]')?.focus()。
Safari 的渲染缺陷比想象中更致命
哪怕只用单层 <dialog></dialog>,Safari 17.6 在以下场景仍可能白屏、错位或焦点失效:
- 父容器有
transform: translateZ(0)或其他硬件加速声明 - 页面启用了
scroll-behavior: smooth,滚动时触发 backdrop 渲染异常 - 同时存在多个
position: fixed元素(比如导航栏 + 弹窗),容易触发渲染层 bug
真要支持 Safari,必须运行时检测:typeof HTMLDialogElement !== 'undefined' && 'showModal' in HTMLDialogElement.prototype;不支持就整套降级,别只 fallback 遮罩层——焦点管理、inert、Esc、Backdrop 点击逻辑一个都不能少。











