dialog标签需手动补全语义(aria-labelledby/label)、焦点(autofocus+显式focus)和状态(aria-modal="true"+aria-hidden同步),否则屏幕阅读器将跳过、误读或无法操作。

dialog 标签本身不自动满足屏幕阅读器友好要求,必须手动补全语义、焦点和状态三要素,否则 NVDA、VoiceOver 等读屏工具会跳过、误读或无法操作。
必须设 aria-labelledby 或 aria-label 提供可访问名称
只写 <dialog></dialog>,屏幕阅读器只会朗读“对话框”,不告诉你这是什么内容。没有标题引用,等于没开门牌号。
-
aria-labelledby是首选:指向一个真实、可见、非display: none的标题元素(如<h2 id="share-title">分享链接</h2>),ID 必须严格匹配 - 避免用
aria-label替代——它是静态字符串,无法响应 DOM 文本更新,且会覆盖内部 HTML 内容 - 若弹窗无视觉标题(比如纯图标确认框),才考虑
aria-label="确认删除",但需额外加aria-describedby补充上下文
必须配 aria-modal="true" 并确保 DOM 位置合法
仅写 role="dialog" 不够,aria-modal="true" 才是告诉辅助技术“背景该静音了”。但这个属性在 Safari 中尤其脆弱。
-
aria-modal="true"必须直接写在<dialog></dialog>标签上,不能放在子容器里 -
<dialog></dialog>必须是的直接子元素;嵌套在<div id="app"> 或 <code><main></main>里,Safari 会彻底忽略aria-modal,背景仍可被读取 - 旧版 Firefox 和部分 Android WebView 对
aria-modal支持不稳定,建议双保险:打开时给主内容容器(如#app)加aria-hidden="true",关闭时移除 - 打开后立即执行
dialog.querySelector('button, input, [tabindex="0"]').focus(),推荐显式加autofocus属性 - 监听
keydown捕获 Tab 键:当焦点在最后一个可聚焦项时按 Tab,跳回第一个;Shift+Tab 则反向——aria-modal="true"不提供这个循环逻辑 - 关闭后必须调用触发按钮的
.focus(),否则键盘用户焦点丢失,页面后续操作中断 - 监听
dialog元素自身的click事件,再用e.clientX和dialog.getBoundingClientRect()判断是否点在遮罩区域外 -
Esc关闭必须触发dialog.close(),并同步恢复焦点;不要拦截keydown事件阻止 Esc,这会破坏屏幕阅读器的全局快捷键 - 关闭后检查
dialog.returnValue—— Safari 中它始终为空字符串,别拿它传值;表单提交请用<form method="dialog"></form>并确保按钮有value属性
焦点必须手动接管,不能依赖默认行为
showModal() 会尝试聚焦第一个可聚焦子元素,但这个“尝试”不可靠——如果内部没有 button、input 或带 tabindex="0" 的元素,焦点可能落在 body 上,模态体验直接崩塌。
关闭事件与 backdrop 点击需统一处理
dialog::backdrop 是伪元素,不能绑事件,也不能用 e.target === dialog 判断点击位置——Safari 下这个判断基本失效。
最常被跳过的其实是焦点恢复和 aria-hidden 同步:关掉弹窗后忘了把焦点设回原按钮,或者没清理背景容器的 aria-hidden="true",会导致后续所有内容对读屏用户不可见——这两个动作不是“锦上添花”,而是可访问性的底线。











