role="dialog" 必须配合 aria-modal="true" 才语义完整,否则屏幕阅读器不暂停背景交互、不自动聚焦;还需手动实现焦点陷阱、正确设置 aria-labelledby/aria-describedby 及 backdrop 的 aria-hidden。

role="dialog" 必须配合 aria-modal="true" 才算语义完整
只写 role="dialog" 而不加 aria-modal="true",屏幕阅读器(如 NVDA、VoiceOver)通常不会暂停背景内容的交互,也不会自动聚焦到对话框内,用户可能完全感知不到模态框已打开。WAI-ARIA 1.1 明确要求:可聚焦、阻断背景交互的模态对话框,必须同时设置这两个属性。
实操建议:
立即学习“前端免费学习笔记(深入)”;
-
aria-modal="true"应直接写在role="dialog"元素上,不能放在其子容器里 - 不要用
aria-modal="false"—— 它等价于不写,且部分读屏会忽略该值 - 若对话框支持非模态模式(比如可后台操作),应动态切换
aria-modal值,并同步管理焦点和 backdrop 遮罩
必须手动处理焦点陷阱(focus trap)
aria-modal="true" 不会自动限制 Tab 键在对话框内循环,也不阻止 Shift+Tab 跳出。浏览器仅靠该属性无法实现真正的“模态”行为,焦点仍会流到背景元素。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 对话框显示后,立即用
element.focus()将焦点设到第一个可聚焦子元素(如input或带tabindex="0"的按钮) - 监听
keydown事件,捕获 Tab 键,手动将焦点在可聚焦子元素间循环(可用element.querySelectorAll('[href], button, input, select, textarea, [tabindex]')获取) - 关闭对话框时,必须把焦点恢复到触发它的元素(例如原
button),否则读屏用户容易迷失上下文
需要显式添加 aria-labelledby 和 aria-describedby
仅靠 role="dialog" 和 aria-modal="true" 无法告诉屏幕阅读器“这是什么对话框”。缺少标题引用会导致读屏只朗读“对话框”,无实质信息。
实操建议:
立即学习“前端免费学习笔记(深入)”;
-
aria-labelledby指向对话框内唯一、可见的标题元素(如h2),ID 必须匹配,且该标题不能display: none或aria-hidden="true" - 若对话框有说明性文字(非标题),用
aria-describedby指向对应元素;若没有,则可省略,但不可指向空节点或不存在的 ID - 避免用
aria-label替代aria-labelledby—— 后者支持 HTML 内容更新,前者是静态字符串,且会覆盖内部文本
Backdrop 遮罩层不能遗漏 aria-hidden
常见的错误是给遮罩层(overlay)加 role="presentation" 或什么都不加,导致屏幕阅读器仍能遍历它背后的页面元素,或把遮罩本身当作一个可交互对象朗读。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 遮罩层应设
aria-hidden="true",且当对话框关闭时,需同步移除该属性或销毁节点 - 遮罩层本身不应有
tabindex,也不应响应键盘事件(除非用于点击关闭) - 如果遮罩层包含关闭按钮(如叉号图标),该按钮必须有
aria-label="关闭对话框"或包裹在button中,不能仅靠视觉符号
aria-hidden、aria-modal、DOM 存在性,四者只要一个脱节,辅助技术体验就立刻断裂。



















