弹窗打开时只给主内容区加inert,不可给body加——否则弹窗、toast、loading全被禁用;正确结构是与同级,JS须用.inert=true/false赋值,禁用后元素在交互链路和可访问性树中彻底“不存在”。

弹窗打开时只给加inert,别碰
给
加inert是最常见的错误——它会把弹窗、toast、全局 loading 遮罩全锁死,用户点不了关闭按钮、Tab 键进不了输入框、屏幕阅读器读不到弹窗内容。正确做法是:只让主内容区失活,浮层保持自由。
DOM 结构必须满足兄弟关系:<main inert></main> 和 <dialog></dialog>(或自定义弹窗)同级,都在 <div id="app"> 下。这样 <code>inert 只作用于 <main></main> 及其子树,不影响弹窗本身。
JS 控制必须用 .inert = true/false,不是 setAttribute
setAttribute('inert', '')、toggleAttribute('inert')、甚至 el.inert = 'true' 全部无效。浏览器只认布尔赋值,且行为是原子级的:设为 true 会立刻退出焦点、阻断事件冒泡、从可访问性树移除整棵子树。
- ✅ 正确:
document.getElementById('main-content').inert = isModalOpen
- ❌ 无效:
el.setAttribute('inert', 'true')、el.inert = 'true'、el.removeAttribute('inert')
- ⚠️ 注意:如果用 React/Vue,SSR 输出的 HTML 必须初始带
inert 属性,否则 hydration 时触发 “prop mismatch”
旧浏览器不支持 inert,兜底不能只靠 pointer-events: none
'inert' in HTMLElement.prototype 在 Chrome 111+、Firefox 121+、Safari 18.0+ 才返回 true。Safari 17、旧 Edge、部分安卓 WebView 完全无视该属性,且 polyfill(如 @webcomponents/inert)无法补全焦点重定向和 AT 树移除。
必须手动降级:
- 对目标容器设
element.setAttribute('aria-hidden', 'true') + element.tabIndex = -1
- 递归遍历所有可聚焦子元素(
button、a[href]、input、[tabindex]),逐个设 tabIndex = -1
- 监听
focusin,检测焦点是否落入禁用区域,强制 focus() 到弹窗内首个可交互元素
- 别只加
pointer-events: none——它挡不住 Tab、Enter 触发 click、或 JS 主动调用 .focus()
inert 不影响滚动,但会影响焦点流和可访问性树
很多人误以为 inert 是视觉禁用或事件拦截开关,其实它是 DOM 层级的“存在性开关”:一旦启用,那一块子树在浏览器眼里就相当于不存在于交互链路和可访问性上下文中。
它不改变布局、不隐藏内容、不阻止滚动(只要滚动容器是 <main></main> 自身,加了 inert 后仍可滚),但会彻底切断键盘导航路径和屏幕阅读器感知。真正容易被忽略的是:虚拟键盘弹出时(比如移动端 input 获焦),某些浏览器可能重排焦点流,导致 inert 区域意外“松动”,需额外校验焦点位置。
给
加inert是最常见的错误——它会把弹窗、toast、全局 loading 遮罩全锁死,用户点不了关闭按钮、Tab 键进不了输入框、屏幕阅读器读不到弹窗内容。正确做法是:只让主内容区失活,浮层保持自由。
DOM 结构必须满足兄弟关系:<main inert></main> 和 <dialog></dialog>(或自定义弹窗)同级,都在 <div id="app"> 下。这样 <code>inert 只作用于 <main></main> 及其子树,不影响弹窗本身。
JS 控制必须用 .inert = true/false,不是 setAttribute
setAttribute('inert', '')、toggleAttribute('inert')、甚至 el.inert = 'true' 全部无效。浏览器只认布尔赋值,且行为是原子级的:设为 true 会立刻退出焦点、阻断事件冒泡、从可访问性树移除整棵子树。
- ✅ 正确:
document.getElementById('main-content').inert = isModalOpen - ❌ 无效:
el.setAttribute('inert', 'true')、el.inert = 'true'、el.removeAttribute('inert') - ⚠️ 注意:如果用 React/Vue,SSR 输出的 HTML 必须初始带
inert属性,否则 hydration 时触发 “prop mismatch”
旧浏览器不支持 inert,兜底不能只靠 pointer-events: none
'inert' in HTMLElement.prototype 在 Chrome 111+、Firefox 121+、Safari 18.0+ 才返回 true。Safari 17、旧 Edge、部分安卓 WebView 完全无视该属性,且 polyfill(如 @webcomponents/inert)无法补全焦点重定向和 AT 树移除。
必须手动降级:
- 对目标容器设
element.setAttribute('aria-hidden', 'true')+element.tabIndex = -1 - 递归遍历所有可聚焦子元素(
button、a[href]、input、[tabindex]),逐个设tabIndex = -1 - 监听
focusin,检测焦点是否落入禁用区域,强制focus()到弹窗内首个可交互元素 - 别只加
pointer-events: none——它挡不住 Tab、Enter 触发 click、或 JS 主动调用.focus()
inert 不影响滚动,但会影响焦点流和可访问性树
很多人误以为 inert 是视觉禁用或事件拦截开关,其实它是 DOM 层级的“存在性开关”:一旦启用,那一块子树在浏览器眼里就相当于不存在于交互链路和可访问性上下文中。
它不改变布局、不隐藏内容、不阻止滚动(只要滚动容器是 <main></main> 自身,加了 inert 后仍可滚),但会彻底切断键盘导航路径和屏幕阅读器感知。真正容易被忽略的是:虚拟键盘弹出时(比如移动端 input 获焦),某些浏览器可能重排焦点流,导致 inert 区域意外“松动”,需额外校验焦点位置。











