ARIA 属性是语义补充而非可访问开关,仅在原生 HTML 语义不足时使用;盲目添加会破坏读屏体验。必须为纯图标按钮、空元素或仅含 SVG 的控件添加 aria-label 或 aria-labelledby,且二者不可共存;表单输入框须用 而非 aria-label。aria-expanded 仅接受 "true"、"false" 或省略,须与 JS 行为同步更新。aria-hidden="true" 会彻底移除节点及其子树的可访问性,不控制视觉显示,父子冲突时父级强制生效。role 必须成套使用并配合 JS 实现对应交互逻辑,ARIA 本身不改变行为,仅传递语义。

ARIA 属性不是“加了就可访问”的开关,而是当 HTML 原生语义无法准确表达组件行为或状态时,才必须补上的语义注释;盲目添加反而会破坏读屏体验。
哪些元素必须加 aria-label 或 aria-labelledby?
纯图标按钮、空
-
aria-label用于无可见文本的场景,如<button aria-label="搜索"><svg></svg></button> -
aria-labelledby用于已有可见文本的场景,比如图标旁有“用户名”文字,就用aria-labelledby="username-label"指向那个<label id="username-label">用户名</label> - 别在同一个元素上同时写
aria-label和aria-labelledby——后者会被静默忽略 - 表单输入框禁止用
aria-label替代<label></label>,否则键盘用户无法用标签聚焦
aria-expanded 的合法值和常见错用
aria-expanded 是布尔型状态属性,只接受字符串 "true"、"false" 或完全省略(不可写 "open"、"active" 或 JS 变量名):
- Vue/React 中绑定时建议显式转字符串:
:aria-expanded="isOpen ? 'true' : 'false',避免undefined或null渲染出非法值 - 错误示例:
<button aria-expanded="active"></button>—— 浏览器会忽略该属性,读屏器无法感知状态变化 - 它只适用于控制另一区域显隐的触发器(如下拉按钮、手风琴标题),不能加在被控制的菜单容器上
- 必须与 JS 行为严格同步:点击后不仅要显示菜单,还要把
aria-expanded改为"true",且对应菜单需设aria-hidden="false"
aria-hidden="true" 的真实作用和高危误用
aria-hidden="true" 不是视觉隐藏指令,它只告诉辅助技术“从可访问树中彻底移除这个节点及其所有子节点”,但视觉和 JS 仍照常工作:
立即学习“前端免费学习笔记(深入)”;
- 父元素设了
aria-hidden="true",子元素再写aria-hidden="false"完全无效——继承强制生效 - 别对整个
<nav></nav>或加它来“隐藏背景”,这会导致键盘焦点能进、读屏器却跳过,形成焦点陷阱 - 真正需要“视觉隐藏但读屏可读”时,用 CSS 类(如
.sr-only)移出视口,而不是aria-hidden - 给图标加
aria-hidden="true"是安全的,前提是旁边已有可见文字(如“设置”文字 + 齿轮图标)
role 和 aria-* 必须成套使用,不能只写一半
单独写 role="listbox" 或 role="dialog" 几乎没用,浏览器和读屏器会报错或直接忽略:
- 自定义下拉必须配齐:
role="listbox"+ 子项role="option"+ 触发按钮aria-haspopup="listbox"+aria-expanded+aria-activedescendant - 模态框必须三者同在:
role="dialog"+aria-modal="true"+aria-labelledby="title-id",缺一不可 - 标签页要联动:
role="tablist"→ 每个role="tab"带aria-selected→ 对应面板用aria-controls关联 - 所有
role都必须有 JS 同步行为:比如role="switch"要监听 Space 键、更新aria-checked、并给出视觉反馈
最常被忽略的一点:ARIA 属性本身不改变任何行为,它只是“说人话”;JS 必须确保 DOM 状态、属性值、视觉表现三者严格一致,否则读屏器读的和用户看到的完全是两回事。



















