什么是 Web 无障碍

无障碍(Accessibility,简称 a11y)指让网站对尽可能多的人可用:视力障碍者靠读屏软件听页面,行动障碍者靠键盘或辅助设备操作,色弱用户靠对比度区分信息,还有暂时性的场景——阳光刺眼、一只手抱娃、网速慢。无障碍不是「照顾少数人」的特殊功能,而是产品可用性的底线。

行业标准是 W3C 的 WCAG(Web Content Accessibility Guidelines),按 A、AA、AAA 三级划分要求,绝大多数产品以 AA 为目标。它围绕四条原则组织:可感知、可操作、可理解、健壮。前端做无障碍不是「写完再补」,而是在结构、交互、视觉三个层面从设计时就纳入约束。

<img src="hero.png" alt="春季新品发布主视觉">
<button type="button">提交订单</button>

上面两行是「无障碍默认正确」的写法:图片有替代文本,按钮用原生元素自带键盘与语义。多数无障碍问题恰恰来自反着做——用 `div` 模拟按钮、图片不给 alt、颜色当唯一信号。

无障碍不是功能,是「基本可用」本身。做得对时,所有人都是受益者。
本文核心知识点
  • WCAG四大原则与 AA 标准。
  • 语义化HTML 与 ARIA。
  • 键盘导航与焦点管理。
  • axe自动化检测。
Web 无障碍配图:蓝调插画中人物头部肩部轮廓剪影,上方同心圆科技纹路与方形透明模块散布,体现辅助技术与可感知设计
辅助技术读的不是视觉,是语义结构:页面要能被「听」懂。

语义化 HTML 是无障碍的地基

读屏软件和搜索引擎一样,靠 HTML 语义理解页面。`header`、`nav`、`main`、`article`、`aside`、`footer` 这些 landmark 让辅助技术可以快速跳转;`h1` 到 `h6` 的层级是页面的「目录」;`ul` 表示列表、`table` 表示表格。用对元素,无障碍一半就完成了。

<nav aria-label="主导航">
  <a href="#overview">概览</a>
  <a href="#guide">指南</a>
</nav>

<button type="button">立即下载</button>
<!-- 不要用 div + onclick 模拟按钮 -->

反过来,滥用 `div` 和 `span` 会抹掉所有语义,读屏用户听到的只是一串无结构的文本。经验法则:能用原生元素就不用自定义组件;`a` 用于跳转、`button` 用于动作,两者不要互换。

ARIA 角色与属性

原生元素表达不了的复杂控件,用 ARIA 补齐语义。`aria-label` 给无文本的图标按钮补名字;`aria-live` 让动态更新的区域被读屏实时播报;`aria-expanded` 表明折叠面板状态;`role` 声明组件的语义角色。

<button class="icon-btn" aria-label="关闭弹窗">
  <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3l9 5v8l-9 5-9-5V8z" fill="currentColor"/></svg>
</button>

<div role="alert" aria-live="assertive">保存失败,请重试</div>

ARIA 的第一条规则是「能不用就不用」:优先原生语义,ARIA 只做补充。第二条是「写了就要维护」:`aria-expanded`、`aria-checked` 这类状态必须在交互后同步更新,状态失真比没有更糟。第三条,`aria-hidden="true"` 只用于纯装饰内容,绝不能用在可聚焦元素上。

键盘导航与焦点管理

键盘是很多用户的唯一输入方式。基本要求:所有可交互元素可用 Tab 到达、Enter 或空格触发;焦点环(focus ring)清晰可见;弹窗打开时焦点移入、关闭后归还;长页面提供「跳到主要内容」的跳过链接。

<a class="skip-link" href="#main">跳到主要内容</a>

// 弹窗关闭后归还焦点
function closeDialog() {
  dialog.hidden = true;
  triggerBtn.focus();
}

`tabindex` 要克制:`-1` 让元素可编程聚焦(配合脚本移动焦点)、但不进 Tab 顺序;`0` 表示可 Tab;大于 `0` 的正值会打乱自然顺序,应尽量避免。焦点管理是「程序员的秩序感」:每一次交互后,焦点落在哪里,都要有意识决定。

表单无障碍

表单是障碍重灾区:没有 label 的输入框读屏只能念「编辑框」;校验错误只靠红框,色盲用户无从分辨。正确做法是每个输入都有关联的 `label`,错误信息用文本 + `aria-describedby` 关联到字段,必填标记用文字而不是只用星号。

<label for="email">邮箱地址</label>
<input id="email" type="email" required
       aria-describedby="email-err">
<p id="email-err" role="alert">请输入正确的邮箱格式</p>

相关选项用 `fieldset` 和 `legend` 分组,读屏会先念组名再念选项。错误提示不仅要有颜色变化,还要有文字说明;提交后把焦点移到第一条错误上,用户不用从头摸索。