原生 <dialog> 标签需手动补全 aria-labelledby、焦点管理及 backdrop 的 aria-hidden 才能通过 WCAG 2.1 AA 测试。

dialog 标签默认不自动满足 WAI-ARIA 合规要求
原生 <dialog> 确实自带 role="dialog" 和 aria-modal="true",但仅靠这些不足以通过 WCAG 2.1 AA 级测试。浏览器不会自动设置 aria-labelledby,也不会在打开时可靠聚焦首个可聚焦元素,更不会为 backdrop 设置 aria-hidden="true" —— 这些都得手动补全。
必须显式设置 aria-labelledby 指向可见标题
没有 aria-labelledby,屏幕阅读器只会朗读“对话框”,不读标题内容。哪怕你写了 <h2>删除确认</h2>,若没用 id 关联,它就是“哑巴标题”。
正确做法:
-
<h2 id="delete-title">删除确认</h2>必须存在且可见(不能display: none或aria-hidden="true") -
<dialog aria-labelledby="delete-title">中的 ID 必须完全匹配,大小写敏感 - 避免用
aria-label替代——它无法响应 DOM 文本更新,且会覆盖视觉标题语义 - 若标题含动态内容(如“删除「用户A」?”),
aria-labelledby仍有效;aria-label则需 JS 手动同步,易出错
showModal() 后必须立即接管焦点并 trap Tab
.showModal() 触发后,浏览器尝试聚焦第一个可聚焦子元素,但这个行为不稳定:可能聚焦到 <button>,也可能跳过所有元素停在 <dialog> 自身(不可聚焦),甚至落到背景。
立即学习“前端免费学习笔记(深入)”;
实操要点:
- 调用
dialog.showModal()后立刻执行firstFocusable.focus(),推荐用querySelector('[autofocus], button, input, [tabindex="0"]')获取首个目标 - 监听
keydown,捕获Tab键:当焦点在最后一个可聚焦项时按 Tab,event.preventDefault()并跳回第一个;Shift+Tab 反向处理 - 关闭弹窗时,必须
triggerButton.focus()—— 不能依赖document.activeElement,它可能已被其他逻辑篡改 - 别忘了给 backdrop(灰色遮罩层)加
tabindex="-1"并focus()它一次?不需要。现代浏览器对<dialog>的 backdrop 不暴露为可聚焦节点,强行聚焦反而破坏行为
关闭逻辑必须同步状态与焦点,而非只调 close()
dialog.close() 只是切换 open 属性,不恢复焦点、不清理 aria-hidden、不重置键盘监听器。无障碍失败常发生在这里。
关键动作:
- 监听
close事件(不是click),在回调里执行焦点还原:triggerBtn.focus() - 若你用
inert属性禁用背景,关闭后必须移除它;若用aria-hidden="true"加在 wrapper 上,也要同步设回false或移除属性 - ESC 关闭和点击 backdrop 都会触发
close事件,无需额外绑定 —— 但要验证是否真触发了(某些 polyfill 会漏) - 不要在
close回调里直接操作dialog.open = false—— 这是冗余的,且可能干扰浏览器原生状态同步
最常被忽略的是:aria-modal="true" 在 Safari 和旧版 Firefox 中实际无效,所以 backdrop 的 aria-hidden="true" 不是可选项,而是保底必需项。别信“用了 dialog 就万事大吉”——合规性永远落在细节执行上。



















