aria-modal="true" 仅是语义标记,无法阻止背景焦点,必须配合 role="dialog"、手动焦点管理(打开时聚焦首元素、拦截Tab键、关闭后恢复触发源焦点)及安全的 aria-hidden 设置才能实现完整模态体验。

aria-modal="true" 不能阻止背景元素获取焦点。
它只是语义标记,告诉屏幕阅读器“当前应只关注这个区域”,对浏览器原生 Tab 导航行为完全无影响。Safari(全版本)、旧版 Firefox、部分 NVDA 配置下,该属性甚至被直接忽略——用户按 Tab 仍会跳到页脚、导航栏、body 上,键盘用户当场卡死。
真正起作用的是手动焦点管理 + 配套语义控制。
aria-modal="true" 必须和 role="dialog" 同时存在
缺一不可,否则读屏器可能根本识别不了这是模态上下文:
立即学习“前端免费学习笔记(深入)”;
- 单独写
aria-modal="true"→ 读屏器不拦截背景朗读 - 单独写
role="dialog"→ 读屏器知道是对话框,但不隔离背景内容 - 两者共存 → 才构成基础语义闭环,但依然不拦 Tab
别把 aria-modal 当成开关,它只是“说明书”,不是“门锁”。
真正阻止背景焦点的三件事
必须同时做,少一个就失效:
打开模态框时,立刻执行
firstFocusable.focus()
不要等 CSS 动画结束,尤其 Safari 下动画期间focus()会失败;推荐用requestAnimationFrame(() => first.focus())或queueMicrotask(() => first.focus())-
监听
keydown拦截Tab和Shift+Tab
获取所有可聚焦元素:Array.from(modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'))注意排除tabindex="-1"元素(它们可被脚本聚焦,但不参与 Tab 流) 关闭后,焦点必须回到触发源,不是
document.body
缓存触发按钮引用(如const triggerBtn = event.target),关闭时检查它是否还存在且可聚焦,再调triggerBtn.focus();用document.body.focus()会导致屏幕阅读器从头朗读页面,用户彻底丢失上下文
背景遮罩和 aria-hidden 怎么设才安全
不要给 body 直接加 aria-hidden="true"——某些读屏器会跳过整页,连模态框都读不到。
正确做法:
- 用一个包裹主内容的
<div id="app"> - 模态框打开时,给
#app加aria-hidden="true" - 关闭时移除,避免后续内容无法被读取
遮罩层(overlay)建议设 tabindex="-1" + aria-hidden="true",否则可能被意外聚焦。
最易被忽略的点:Safari 中 <dialog>.showModal() 只设了 aria-modal="true",但不自动聚焦首个元素,也不围住 Tab 键。你写的每行 JS 焦点逻辑,在 Safari 里都不是“锦上添花”,而是“救命必需”。



















