结论:aria-label、aria-labelledby、aria-describedby 并非“加了就安全”,而是易因误用引发名称冲突、读屏跳过或语义失效的高危点,检测必须基于可访问性树的实际计算结果而非代码存在性。

直接说结论:aria-label、aria-labelledby、aria-describedby 这三个属性不是“加了就安全”,而是最容易因误用导致名称冲突、读屏器跳过或语义覆盖失效的高危点。检测必须基于可访问性树的实际计算结果,而非代码里有没有写。
为什么 inspect 看到 Name 是空的?常见误用链
屏幕阅读器读不出元素名,往往不是漏写了 aria-label,而是多个命名来源相互干扰或被忽略:
-
aria-label和label同时存在:Chrome 优先取aria-label,NVDA 可能只读label内容,行为不一致;删掉其中一个,保留语义最清晰的那个 -
aria-labelledby指向的 ID 不存在或被display: none隐藏:可访问性树里该节点根本不可见,Name 计算失败 → 直接报空 - 图标按钮只靠
alt或纯视觉文字:比如<img src="close.svg">没alt,或<button>✕</button>没任何命名属性 → Name 列显示为空 - 自定义组件用
div+role="button"却没补aria-label:Computed Role 是button,但 Name 仍为空 → 键盘聚焦后读屏器只说“button”,不说用途
Computed Role 显示 generic?先查基础结构,别急着加 ARIA
Role 列显示 generic 不代表 ARIA 写得不够,而是浏览器根本没把它当“有语义的控件”处理:
- 表单控件没配
label:比如<input type="email">孤立存在 → 补<label for="email">邮箱</label><input id="email" type="email"> - 用
div模拟按钮/下拉/开关,但既没加role,也没加键盘事件(tabindex="0"、onkeydown)→ 至少补role="button"+tabindex="0" - CSS 把元素藏掉了:
display: none、visibility: hidden、aria-hidden="true"会让它彻底从可访问性树消失 → 改用视觉隐藏类(如position: absolute; clip: rect(1px, 1px, 1px, 1px);) -
main被包在div里:<div><main>...</main></div>→ NVDA 会跳过整个main,Role 计算为generic
States 不同步?JS 更新 DOM 后必须触发可访问性变更
动态交互中,状态变化(展开/选中/禁用/加载中)如果只改 JS 属性或 class,读屏器大概率无感知:
-
input.checked = true后没触发change事件 → 屏幕阅读器仍读“未选中”。正确做法是input.click()或input.dispatchEvent(new Event('change', { bubbles: true })) - 折叠面板点击后仅切
class="open",没更新aria-expanded→ 读屏器无法获知当前状态。必须同步设置el.setAttribute('aria-expanded', 'true') - 模态框弹出后没设
aria-modal="true",也没把焦点移到第一个可聚焦元素 → 用户按 Tab 会跳出对话框,进入背景页面 - 错误提示用
textContent替换,但没加aria-live="polite"→ 读屏器沉默。需包裹在带aria-live的容器里,或用aria-live区域主动推送
真正难的不是记住属性名,而是理解可访问性树的计算逻辑:它不看你写了什么,只看最终渲染出的 Role / Name / States 三列是否稳定、唯一、同步。每次改交互逻辑,都要右键 → Inspect Accessibility Properties,盯着这三列验证——否则所谓“加了 ARIA”只是自我安慰。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











