自定义非Portal模态框设置document.body.setAttribute('aria-hidden', 'true')无效的问题排查
嘿,这个问题我之前做自定义模态框的时候也纠结过,咱们慢慢拆解来聊~
一、为什么你的document.body.setAttribute('aria-hidden', 'true')会被忽略?
首先得明确:直接给<body>加aria-hidden="true"其实是违背WAI-ARIA指南的——因为你的自定义模态框是嵌套在<body>的子树里(非Portal场景),给父元素加aria-hidden会让所有子元素都被辅助技术忽略,包括你的模态框本身。
这种情况下,浏览器或辅助技术可能为了避免“把整个页面(包括当前交互的模态框)都隐藏”的不合理操作,直接拦截了这个设置请求;另外也有可能是你项目里的其他代码(比如你提到的模态框库、React生命周期逻辑)在你设置之后又把这个属性改回去了,你可以加个console.log(document.body.getAttribute('aria-hidden'))在设置后验证下,看看有没有被覆盖。
二、为什么react-modal要给加aria-hidden?
你贴的react-modal截图我很熟悉,它这么做其实是个“取巧的hack”,但同时做了一堆额外操作补全逻辑,才让这个不符合指南的操作生效:
- 它的模态框是通过Portal直接挂载到
<body>下的,但会给模态框本身单独设置aria-hidden="false",明确打破<body>的aria-hidden继承; - 它强制把焦点锁在模态框内部:打开时自动聚焦到模态框内的可交互元素,关闭时恢复之前的焦点,防止用户用Tab键切到背景内容;
- 还给模态框设置了
role="dialog"、aria-modal="true",以及aria-labelledby/aria-describedby等属性,让辅助技术能正确识别这是一个模态框,优先级高于<body>的隐藏属性。
虽然不符合官方指南,但这些封装的细节让这个hack能正常工作,也是它成为热门库的原因之一。
三、你的自定义模态框该怎么正确实现无障碍?
别学react-modal的这个hack,按WAI-ARIA指南来做才是稳妥的:
- 不要给加aria-hidden,而是给所有背景内容的容器加。比如你的页面内容都在
<div id="app-content">里,打开模态框时给这个容器设置aria-hidden="true",关闭时移除; - 给自定义模态框设置必要的无障碍属性:
<div role="dialog" aria-modal="true" aria-labelledby="modal-title" // 其他样式/逻辑属性 > <h2 id="modal-title">模态框标题</h2> // 模态框内容 </div> - 实现焦点锁定:用
useEffect监听模态框的显示状态,打开时聚焦到模态框内第一个可交互元素(比如确认按钮),关闭时恢复之前的焦点元素; - 可选:给模态框加
inert属性(现代浏览器支持),或者用JS阻止背景内容的交互。
这样既符合无障碍指南,也能让辅助技术正确忽略背景内容,专注于模态框。
备注:内容来源于stack exchange,提问作者aphenine
相关产品推荐
相关产品推荐

