aria-modal="true"必须显式添加,不能依赖role="dialog";需配合focus管理、aria-labelledby、aria-describedby及backdrop的aria-hidden处理,缺一不可。
role="dialog" 本身不隐含模态行为,浏览器和读屏器不会自动将其视为阻断背景交互的模态框。必须手动加 aria-modal="true" 才能正确传达模态语义。
常见错误是只写 <div role="dialog"> 就以为完成了——实际测试中 NVDA、JAWS 或 VoiceOver 仍会朗读并允许聚焦到背景元素,键盘 Tab 也能逃出弹窗。
aria-modal="true" 是独立声明,和 role="dialog" 平级,二者缺一不可false 或移除(推荐移除)aria-modal,但现代主流浏览器(Chrome ≥ 85、Firefox ≥ 76、Safari ≥ 13.1)均已支持即使写了 aria-modal="true",如果用户用键盘 Tab 仍能离开 dialog 内容,屏幕阅读器就会“感知错乱”:它以为这是模态框,但焦点却跑到外面去了。
实操要点:
element.focus() 把焦点移到第一个可聚焦子元素(如 button 或带 tabindex="0" 的容器)keydown 捕获 Tab 键,实现焦点循环(focus trap):按 Shift+Tab 到开头时跳到最后一个可聚焦项,反之亦然button),否则焦点会丢失到 body 顶部仅靠 role="dialog" + aria-modal="true" 不足以构成完整可访问 dialog。缺少标题会导致读屏器无法播报“XX 对话框已打开”。
正确做法:
<h2 id="dialog-title">确认删除</h2>),然后通过 aria-labelledby="dialog-title" 关联aria-describedby="dialog-desc" 指向对应 id
aria-label 替代 aria-labelledby —— 后者支持 HTML 内容,前者只能是纯文本,且无法被翻译或样式化很多实现会在 dialog 下面加一层半透明遮罩(backdrop),用于视觉隔离和点击收起。这个 backdrop 必须明确标记为不可访问,否则读屏器可能误读其内容或干扰焦点流。
正确方式:
aria-hidden="true"
button、a、input),否则 aria-hidden 会被忽略click,而不是在 backdrop 上放 button —— 后者会破坏语义结构<div role="dialog" aria-modal="true" aria-labelledby="dialog-title" aria-describedby="dialog-desc"><div id="dialog-title">警告</div><div id="dialog-desc">此操作不可撤销</div><button>确定</button><button>取消</button></div>真正难的不是写对这几个属性,而是确保所有交互路径(键盘、屏幕阅读器、触控)都走通——尤其 focus trap 的边界处理和焦点恢复逻辑,很容易漏掉某一种关闭方式(Esc、Backdrop 点击、X 按钮)。