aria-hidden="true"仅对辅助技术隐藏元素,不影响视觉呈现;误用会导致错误提示、动态内容等关键信息被屏幕阅读器跳过,正确场景仅限纯装饰性内容或配合inert/display:none临时移出可访问流。

为什么 aria-hidden 用错会直接跳过重要内容
很多开发者以为 aria-hidden="true" 是“隐藏给所有人看”的万能开关,结果导致屏幕阅读器把本该播报的错误提示、动态加载的列表项甚至表单校验信息全跳过了。它只影响辅助技术,不影响视觉渲染——也就是说,用户能看到,但读屏软件完全无视。
正确用法有且仅限于两类场景:
- 纯装饰性内容:比如背景图标、分隔线 SVG,且确认它不携带任何功能或语义
- 临时移出可访问流的 DOM 片段:比如模态框关闭时,配合
display: none或inert使用;但绝不能单独用aria-hidden="true"配合visibility: hidden或opacity: 0
常见错误:<div aria-hidden="true"><span>提交成功</span></div> —— 这个成功提示对键盘+读屏用户等于没发生。
role 覆盖原生语义时的冲突风险
给 <button></button> 加 role="link",或给 <a href="#"></a> 加 role="button",会让屏幕阅读器同时播报“按钮”和“链接”,造成语义矛盾。浏览器和读屏工具优先信任原生标签的隐含行为,强行覆盖只会增加解析歧义。
真正需要干预的,是那些无法用原生元素表达的交互模式:
- 手风琴组件:用
<details><summary></summary></details>,而不是<div role="region"> + 手动管理 <code>aria-expanded - 标签页:优先用
<nav role="tablist"></nav>+<button role="tab"></button>,而非全靠div模拟 - 必须用
div实现的控件:补全tabindex="0"、role、aria-pressed等全套属性,缺一不可 - 只包裹**最小必要更新单元**:比如仅包裹新追加的聊天消息
<li aria-live="polite">新消息</li>,而不是整个<ul></ul> - 避免嵌套
aria-live区域:内层 live 区域可能被外层屏蔽,或触发重复播报 - 状态类更新(如“正在提交…”→“提交成功”)用
aria-live="assertive",但必须配合aria-atomic="true",否则读屏可能只读变化部分,造成语义断裂 - 模态框打开时:调用
element.focus()移到内部第一个button或input - 模态框关闭时:焦点必须回到触发它的元素(记录
document.activeElement或存 ref) - 禁用背景时:用
inert比手动设tabindex="-1"更可靠,但需注意 Safari 目前仍不支持,得降级用aria-hidden="true"+tabindex="-1"组合
记住:role 是兜底,不是起点。先查 MDN 是否已有对应原生元素,再决定是否引入 ARIA。
动态内容更新时 aria-live 的粒度控制
aria-live 不是“开了就完事”,播报时机和内容范围稍有偏差,就会打断用户操作。比如把整个搜索结果区域设为 aria-live="polite",用户还在输入时,每打一个字都触发一次播报,体验极差。
关键控制点:
示例错误:<div aria-live="polite"><p>剩余字符数:<span id="counter">120</span></p></div> —— 每次计数变化都会读“剩余字符数:119”,冗余且干扰。应只对 <span id="counter"></span> 设 aria-live,并设 aria-atomic="false"。
键盘焦点与 inert 的协同失效点
inert 属性能让整个子树不可聚焦、不可交互、不被读屏读取,但它不自动处理焦点转移。模态框弹出后,若只加 inert 到背景,却不把焦点移到模态框内首个可聚焦元素,键盘用户会卡在页面顶部或丢失上下文。
必须同步做三件事:
最容易被忽略的是:当模态框内有多个 tabbable 元素,却没实现焦点陷阱(trap),用户按 Tab 键会跳出模态框,进入背景——这比没加 inert 更危险,因为视觉上背景是灰掉的,但键盘上却能操作。











