inert 应作用于弹窗外的整块 dom 容器而非单个按钮,正确做法是给 body 或主容器动态添加 inert 并确保弹窗不在其内;避免与 aria-hidden 混用,需兼容 firefox 和旧版 safari,推荐优先使用 并降级为 css + js 组合方案。

inert 属性不作用于按钮个体,而是控制整块 DOM 子树
给弹窗底部的某个 button 单独加 inert,它确实点不动,但这不是“让按钮失效”的合理路径。因为 inert 的语义是“该元素及其所有后代对交互完全不可见”——它不是禁用状态(disabled),不参与表单提交,也不触发焦点或键盘导航。真正需要锁住的是弹窗外的整个页面主体,而不是零散找按钮加属性。
常见错误包括:遍历所有 button、a、input 手动设 inert,结果漏掉 div[tabindex="0"] 或自定义可聚焦组件;或者误以为加了 inert 就自动屏蔽屏幕阅读器,却没处理键盘焦点穿透。
- 正确做法是:弹窗打开时,给
document.body或主容器(如#app)动态添加inert属性 - 确保弹窗自身不在该容器内,或显式移除其
inert(例如modalEl.removeAttribute('inert')) - 若使用
<dialog></dialog>,现代浏览器(Chrome 88+、Safari 16.4+)调用showModal()后会自动为背景应用inert,无需手动干预
inert 和 aria-hidden 混用会导致焦点卡死
inert 已隐含对辅助技术的屏蔽逻辑,它会让屏幕阅读器跳过整个子树,也会阻止键盘焦点进入。而 aria-hidden="true" 只影响 ARIA 树,不阻止 Tab 切换或鼠标点击——两者叠加反而可能让焦点“落在看不见的元素上”,造成键盘用户无法退出。
尤其当弹窗用 <div role="dialog"> 实现时,必须配 <code>aria-modal="true",但此时若再给背景加 aria-hidden="true",就和 inert 冗余甚至冲突。
- 只用
inert,别加aria-hidden - 若降级使用 CSS 遮罩(如
.is-modal-open *:not(.modal) { pointer-events: none; }),则必须额外拦截focusin事件并blur(),否则键盘焦点仍能落到被遮盖区域 - 测试时务必用 VoiceOver/NVDA +
Tab,确认焦点不会停在inert区域内的任何节点上
兼容性 fallback 必须覆盖 Safari 15.3 及 Firefox 全系
inert 在 Safari 16.4+ 和 Chrome 88+ 原生支持,Firefox 至今未实现,旧版 Safari(15.3 及更早)也不支持 showModal() 自动 inert 行为。仅靠 inert 会导致这些浏览器下背景仍可点击、键盘可聚焦、屏幕阅读器继续朗读。
polyfill(如 WICG/inert)虽能模拟行为,但它通过遍历 DOM 标记可聚焦节点并临时移除 tabindex、监听事件来实现,高频弹窗场景下有明显性能开销。
- 轻量 fallback:CSS + JS 组合 ——
document.body.style.pointerEvents = 'none'+document.activeElement.blur()+focusin拦截 - 若需保无障碍,优先用
<dialog></dialog>并检测HTMLDialogElement.prototype.showModal是否存在,不存在则走降级逻辑 - 避免在 polyfill 中对整个
body调用querySelectorAll('*'),改用focusableElements = [...document.querySelectorAll('a[href], button, input, select, textarea, [tabindex]')]精准控制
inert 生效的关键不在按钮,而在你锁定的 DOM 范围层级
实际调试中,90% 的“加了 inert 却没用”问题,根源是 DOM 结构没理清:比如把弹窗 appendChild 到某个 position: relative 的卡片内部,结果 body 加了 inert,但弹窗也继承了——它自己也被锁死了;或者弹窗用 createPortal 挂到 body 底部,却忘了移除父容器的 inert。
真正生效的前提,是明确“谁该不可见”和“谁必须保持可操作”这两者的边界。这个边界由 DOM 层级决定,不是靠样式或 JS 临时 patch。
- 弹窗 DOM 节点必须与被 inert 的容器无祖先关系(推荐直接挂到
document.body底部) - 不要在
inert元素内部放需要交互的子元素——哪怕显式写inert="false"也无效,inert不可被子元素取消 - 服务端渲染(SSR)场景下,初始 HTML 中若已含
inert,客户端 hydration 前可能出现闪动,需配合opacity: 0过渡或初始隐藏逻辑











