dialog 标签需手动补全语义声明(role="dialog"与aria-modal="true")、焦点控制(JS主动聚焦首个可聚焦元素)和背景隔离(动态添加aria-hidden="true")才能满足WCAG 2.1 A级要求。

dialog 标签本身不自动构成无障碍模态框,仅靠 showModal() 无法满足 WCAG 2.1 A 级要求。必须手动补全三类属性:语义声明、焦点控制、背景隔离。
role="dialog" 和 aria-modal="true" 必须同时存在
只写 <dialog> 或只调用 showModal(),屏幕阅读器(如 NVDA、VoiceOver)不会识别为对话框。必须显式添加两个属性:
-
role="dialog":告诉辅助技术“这是一个可交互的模态容器” -
aria-modal="true":指示“外部内容应被忽略”,否则 Safari/Firefox 旧版会继续朗读页脚
漏掉任一,标题不朗读、焦点逃逸、背景仍可操作——不是体验问题,是功能失效。
aria-labelledby 必须指向真实 visible heading 元素
aria-labelledby 不是可选装饰,它决定屏幕阅读器朗读什么。常见错误包括:
立即学习“前端免费学习笔记(深入)”;
- 指向空
<h2 id="modal-title"></h2>或纯图标元素(无文字) - 用
aria-label替代,导致视觉标题与语音标题不一致 - ID 不存在、重复、或被
display: none隐藏(aria-labelledby要求目标元素在 DOM 中且可见)
正确写法:<h2 id="confirm-delete-title">确认删除此文件?</h2> + aria-labelledby="confirm-delete-title"。标题必须是原生 heading(h1–h6),不能是 div 加 role="heading"。
aria-hidden="true" 必须动态加在背景容器上
aria-modal="true" 在 Safari 和旧 Firefox 中基本无效。更可靠的做法是双保险:
- 给
<dialog>加aria-modal="true"和role="dialog" - 给包裹主内容的容器(如
<div id="app">)动态添加aria-hidden="true" - 关闭时立刻移除
aria-hidden="true",别对<body>直接设该属性(部分 AT 会跳过整页)
注意:不要在 dialog 打开前就提前加 aria-hidden,否则触发按钮可能被跳过;必须在 showModal() 后、聚焦前完成设置。
autofocus 属性在 dialog 中基本无效
<input autofocus> 或 <button autofocus> 在 dialog 内不会触发聚焦——因为 showModal() 不触发元素重挂载,autofocus 只在初始渲染时生效。
必须用 JS 主动聚焦:
- 获取第一个真正可聚焦元素:
dialog.querySelector('button, input, select, [tabindex="0"]') - 用
requestAnimationFrame(() => el.focus())包一层(比setTimeout(0)更稳) - 避免聚焦
<h3>、<p>、<div tabindex="-1">—— 它们无法被focus()激活
焦点管理不是“锦上添花”,而是键盘用户能否操作的分水岭。Safari 全版本中,Tab 键跳出模态框不是 bug,是你没补这一步。



















