纯图标按钮必须在上设置aria-label,不可写在内;有可见文字时应优先用aria-labelledby关联;禁用状态必须用disabled属性而非css模拟;开关类按钮需同步aria-pressed或aria-expanded;按钮组须用包裹。

纯图标按钮必须加 aria-label,且只能写在 <button></button> 上
没文字的按钮对屏幕阅读器来说就是“未命名按钮”,用户根本不知道点它干啥。这不是体验问题,是 WCAG 4.1.2 强制要求。
常见错误:把 aria-label 写在 <svg></svg> 里,或者只给图标加、不碰外层 <button></button> —— 这样读屏器完全读不到。
-
<button aria-label="删除"><svg aria-hidden="true"></svg></button>✅ 正确 -
<button><svg aria-label="删除" aria-hidden="true"></svg></button>❌ 白写 -
aria-label=""是明确告诉辅助技术“忽略这个按钮”,不是“没写”,千万别留空 -
<svg></svg>必须同步加aria-hidden="true",否则某些读屏器会误读路径描述,造成重复播报
有可见文字时优先用 aria-labelledby,别硬套 aria-label
页面上已有“设置”两个字,就该让按钮跟它挂钩,而不是再写一遍 aria-label="设置"。后者会导致多语言失效、文案更新后不同步,Lighthouse 直接报错。
- 图标旁有
<span id="setting-label">设置</span>→ 按钮写aria-labelledby="setting-label" - 表格每行的“编辑”按钮,label 来自该行姓名单元格(
id="name-123")→ 写aria-labelledby="name-123" -
aria-label和aria-labelledby绝不能共存:aria-label会静默覆盖aria-labelledby,调试时很难发现 -
aria-labelledby支持空格分隔多个 ID,适合组合上下文,比如aria-labelledby="icon desc input"
别用 <div> 模拟按钮,<code><button></button> 原生支持键盘和禁用状态
用 <div role="button" tabindex="0"> 看似能点,但漏掉任意一项就会卡死键盘用户:Tab 进不去、Enter 不触发、disabled 后仍被读作“可点击”。
<ul>
<li>
<code><button></button> 自带聚焦、Enter/Space 触发、disabled 同步、默认焦点样式 —— 这些不是“可选”,是浏览器原生保障
pointer-events: none?不行。必须用 disabled 属性,否则表单提交时可能被包含,读屏器也仍报“可点击”aria-pressed 或 aria-expanded,否则状态对辅助技术不可见导航按钮组要用 <fieldset></fieldset> + <legend></legend>,别用 <div> 包
<p>一堆 <code><button></button> 堆一起,读屏器只会当成独立控件,无法识别这是“频道筛选”或“排序方式”这一组概念。语义缺失直接导致操作逻辑断裂。
-
<fieldset></fieldset> 定义控制域,<legend></legend> 是它的标题,两者必须配合使用,且 <legend></legend> 必须是 <fieldset></fieldset> 的第一个子元素
- 别用
<h3></h3> 替代 <legend></legend> —— 语义不同,辅助技术不认
- 如果标题需视觉隐藏,用
class="sr-only" 配合 CSS,而不是 display: none 或 aria-hidden="true"
- 单选按钮组必须用
<input type="radio">,共享 name,并显式设 checked(否则 JS 查 document.querySelector('input[name="cat"]:checked') 会返回 null)
真实项目里最容易被跳过的,是 <legend></legend> 的位置和 aria-hidden="true" 在 SVG 里的同步。这两个点一漏,整个按钮组或图标按钮的无障碍就形同虚设——不是“不太好”,而是“对部分用户完全不可用”。
<fieldset></fieldset> 定义控制域,<legend></legend> 是它的标题,两者必须配合使用,且 <legend></legend> 必须是 <fieldset></fieldset> 的第一个子元素<h3></h3> 替代 <legend></legend> —— 语义不同,辅助技术不认class="sr-only" 配合 CSS,而不是 display: none 或 aria-hidden="true"
<input type="radio">,共享 name,并显式设 checked(否则 JS 查 document.querySelector('input[name="cat"]:checked') 会返回 null)











