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

纯图标按钮必须加 aria-label,且只能写在 <button> 上
没文字的按钮对屏幕阅读器来说就是“未命名按钮”,用户根本不知道点它干啥。这不是体验问题,是 WCAG 4.1.2 强制要求。
常见错误:把 aria-label 写在 <svg> 里,或者只给图标加、不碰外层 <button> —— 这样读屏器完全读不到。
-
<button aria-label="删除"><svg aria-hidden="true"></svg></button>✅ 正确 -
<button><svg aria-label="删除" aria-hidden="true"></svg></button>❌ 白写 -
aria-label=""是明确告诉辅助技术“忽略这个按钮”,不是“没写”,千万别留空 -
<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> 模拟按钮,<button> 原生支持键盘和禁用状态
用 <div role="button" tabindex="0"> 看似能点,但漏掉任意一项就会卡死键盘用户:Tab 进不去、Enter 不触发、disabled 后仍被读作“可点击”。
立即学习“前端免费学习笔记(深入)”;
-
<button>自带聚焦、Enter/Space 触发、disabled同步、默认焦点样式 —— 这些不是“可选”,是浏览器原生保障 - 禁用时只改 CSS 灰度 +
pointer-events: none?不行。必须用disabled属性,否则表单提交时可能被包含,读屏器也仍报“可点击” - 开关类按钮要同步
aria-pressed或aria-expanded,否则状态对辅助技术不可见 - 检查关联是否生效:Chrome DevTools → Accessibility 面板 → 点击元素 → 看 “Accessible Name” 字段是否显示预期文本
导航按钮组要用 <fieldset> + <legend>,别用 <div> 包
一堆 <button> 堆一起,读屏器只会当成独立控件,无法识别这是“频道筛选”或“排序方式”这一组概念。语义缺失直接导致操作逻辑断裂。
-
<fieldset>定义控制域,<legend>是它的标题,两者必须配合使用,且<legend>必须是<fieldset>的第一个子元素 - 别用
<h3>替代<legend>—— 语义不同,辅助技术不认 - 如果标题需视觉隐藏,用
class="sr-only"配合 CSS,而不是display: none或aria-hidden="true" - 单选按钮组必须用
<input type="radio">,共享name,并显式设checked(否则 JS 查document.querySelector('input[name="cat"]:checked')会返回null)
<legend> 的位置和 aria-hidden="true" 在 SVG 里的同步。这两个点一漏,整个按钮组或图标按钮的无障碍就形同虚设——不是“不太好”,而是“对部分用户完全不可用”。



















