原生无需添加role="button"或aria-disabled,正确做法是直接使用disabled属性;纯图标按钮必须用aria-label或aria-labelledby提供可访问名称;非语义化标签模拟按钮时须补全role、tabindex和键盘事件监听。

原生 <button></button> 就是辨识度最高的按钮,别画蛇添足加 role="button" 或 aria-disabled;真正需要动手的,是那些非语义化标签模拟的按钮、纯图标按钮、或动态禁用场景。
为什么别给 <button></button> 加 role="button"
浏览器对 <button></button> 有内置语义、焦点管理、Enter/Space 响应和表单行为。加 role="button" 不但多余,还可能触发校验警告,或导致读屏器重复播报(比如“按钮,按钮”)。更危险的是,有人顺手写 aria-disabled="true" 却忘了同步设 disabled 属性——结果视觉灰了,读屏器仍说“可点击”,状态彻底错乱。
正确做法只有一条:<button disabled>提交</button>。JS 动态控制时,也只改 el.disabled = true,不要只动 ARIA 属性。
纯图标按钮必须配 aria-label 或 aria-labelledby
当按钮里只有 <svg></svg>、Emoji 或背景图,没有可见文字时,屏幕阅读器无法推断功能。不能靠 CSS 隐藏文字“取巧”,必须显式提供可访问名称:
如果你了解HTML,CSS和JavaScript,您已经拥有所需的工具开发Android应用程序。本动手本书展示了如何使用这些开源web标准设计和建造,可适应任何Android设备的应用程序 - 无需使用Java。您将学习如何创建一个在您选择的平台的Android友好的网络应用程序,然后转换与自由PhoneGap框架到一个原生的Android应用程序。了解为什么设备无关的移动应用是未来的潮流,并开始构建应用程序,提供更
- 旁边有可见文本(如“搜索”文字紧邻图标)→ 用
aria-labelledby="search-text-id" - 完全无可见文本(如工具栏齿轮图标)→ 用
aria-label="设置",且字符串必须准确反映操作意图(不是“齿轮”,而是“打开设置”) - 切忌写成
<button aria-label="设置">⚙️</button>同时又在<svg></svg>上设aria-hidden="false"——aria-hidden="true"才是正确配对,否则图标可能被重复朗读
用 <div> 或 <code><span></span> 模拟按钮时,三件事缺一不可
放弃原生 <button></button>,等于主动放弃所有默认保障,你得手动补全整个交互链路:
- 加
role="button"告诉读屏器“这是按钮” - 加
tabindex="0"让它能被 Tab 键聚焦 - 监听
keydown,对Enter和Space都触发相同逻辑(只响应Enter是常见坑)
漏掉任一环节,键盘用户就会卡住:能聚焦、不能触发;或能触发、但读屏器不报状态变化。更隐蔽的问题是,这类伪按钮在表单中不会参与 form.submit(),也不响应 click 冒泡——除非你手动绑定并 preventDefault()。
aria-label 和 aria-labelledby 别混用、别覆盖真实文本
这两个属性都会覆盖元素内部文本,形成最终的“可访问名称”。一旦写错,用户听到的和看到的就不一致:
- 错误:
<button aria-label="确认">删除</button>→ 读屏器只读“确认”,用户不知道点的是“删除”还是“确认删除” - 正确:
<button>删除</button>(原生语义足够),或<button aria-label="永久删除">删除</button> - 优先用
aria-labelledby:它引用已有可见文案,支持多语言切换、内容自动同步;aria-label是硬编码,改 UI 时容易脱节 - 绝对不要同时写两个——
aria-labelledby会被忽略,Lighthouse 也会报错
最常被忽略的其实是「动态禁用」和「纯图标按钮的语义一致性」:前者容易只改 ARIA 状态,后者容易把图标含义写得模糊或与视觉不符。这两处出错,键盘用户和读屏器用户会直接失去操作依据。










