ARIA属性仅在原生语义无法表达行为或状态时必须使用,用错比不用更糟;必须用于自定义控件、动态内容更新、状态实时同步三类场景,其余多数情况属画蛇添足。
ARIA 属性不是“加了就无障碍”,它只在原生语义无法表达行为或状态时才该出现;用错比不用更糟,尤其在自定义下拉、模态框、标签页这类组件里。
真正需要 ARIA 的只有三类场景:自定义控件(如 div 实现的下拉)、动态内容更新(如 AJAX 加载列表)、状态实时同步(如折叠面板展开态)。其余多数情况属于画蛇添足:
<button>提交</button> 再加 aria-label="提交表单":覆盖原生文本,导致重复播报或只读 label<header> 加 role="banner":HTML5 已隐含该 role,重复声明可能被忽略或触发警告<h2>设置</h2> 加 aria-label="用户设置页面":标题可访问名称就是自身文本,覆盖后信息丢失<img src="logo.png"> 加 aria-label:应该用 alt,aria-label 在 img 上无效或行为不可靠只写 role="listbox" 不够,浏览器和读屏器会报“缺少必需子角色”或直接忽略。它需要完整语义链:
role="listbox" 必须包裹 role="option"(不能是 div 或 li)aria-haspopup="listbox" 和 aria-expanded="false",且 JS 必须同步更新该值tabindex 暴力推进——得用 aria-activedescendant 指向当前高亮项的 ID,并监听 ArrowUp/ArrowDown 键手动切换innerHTML = 替换整个下拉内容,aria-activedescendant 会失效,必须用 appendChild 或 insertAdjacentElement
仅加 role="dialog" 不足以让辅助技术正确识别模态上下文。常见错误是给 <body> 加 aria-hidden="true",结果整个页面对屏幕阅读器彻底不可见:
立即学习“前端免费学习笔记(深入)”;
role="dialog" + aria-modal="true" + aria-labelledby(三者缺一不可)aria-labelledby 必须指向一个**真实存在的、可见的、有语义的标题元素**(如 <h2 id="modal-title">确认删除</h2>),不能是空 span 或 JS 动态生成后未插入 DOM 的节点focus() 主动将焦点移到首个可聚焦元素(通常是取消按钮或标题),否则键盘用户卡在背景<body> 加 aria-hidden="true"——这会让整个页面从可访问树中消失;正确做法是给背景容器加 inert 或用 tabindex="-1" + 焦点捕获aria-live="polite" 不是“加了就生效”,它依赖更新方式和插入位置。最常踩的坑是把新内容塞进错误的地方:
aria-live 容器内**(比如 container.appendChild(newEl)),不要用 innerHTML = 替换整个容器,否则旧引用丢失aria-live 检测机制aria-live="assertive" 用于错误或关键提示,但慎用,会打断当前朗读;aria-live="polite" 适合非紧急更新(如列表追加)aria-live 区域里嵌套交互控件(如按钮),读屏器可能跳过或误读其状态所有 aria- 属性一旦出现,就必须由 JS 全程维护:点击后没改 aria-expanded,等于没写;ID 引用拼错或元素被移除,读屏器就静默或乱读。这不是配置问题,是逻辑契约。