学完这篇,你能把页面从「看着没问题、读屏软件进不去」改到「键盘和屏幕阅读器都能顺畅用」,并且知道哪些 ARIA 该用、哪些纯属添乱。
第一步:能用原生标签就别动 ARIA
无障碍的第一原则是语义化优先。浏览器和读屏软件对原生元素的角色、状态、键盘行为都有内置支持,你手写的 ARIA 反而容易漏。
- 按钮用
<button>,不要 <div onclick="...">
- 页面结构用
<header> <nav> <main> <aside> <footer>
- 表单每一项都配
<label for="id">,for 与 id 必须严格对应
- 一个页面只放一个
<main>
<!-- 差 -->
<div class="btn" onclick="submit()">提交</div>
<!-- 好 -->
<button type="submit">提交</button>
注意:<div> 加 role="button" 只是「告诉」读屏软件它是个按钮,浏览器不会因此给它键盘响应。你还得手动加 tabindex="0" 并监听 Enter 和 Space 键——这就是为什么直接换回 <button> 更省事。
第二步:标题层级与地标区域
标题是读屏用户最常用的导航方式(NVDA 按 H 键逐级跳标题)。规则很简单:h1~h6 按顺序,不跳级。
- 页面主标题一个
h1,各区块 h2,子区块 h3
- 不要为了字号好看乱选标题,样式交给 CSS
- 地标区域不要滥用:多个
<nav> 建议用 aria-label="主导航" / "面包屑" 区分
第三步:可访问名称的优先级
每个交互元素都要有「名字」,优先级从高到低是:
aria-labelledby(指向元素 id)> aria-label(直接写字符串)> 原生 <label> / alt / 文本内容 > title
图片的 alt 分三种情况:
- 有信息量:
alt="2024 年注册用户增长曲线"
- 纯装饰:
alt=""(必须写空,不是省略)
- 图标按钮:
<button aria-label="关闭弹窗"><svg .../></button>
注意:aria-labelledby 的值是元素的 id,不是文案本身。写 aria-labelledby="关闭弹窗" 而页面上没有 id="关闭弹窗" 的元素,等于没写。
第四步:状态类 ARIA,配合 JS 一起改
折叠菜单、选项卡这类组件,ARIA 负责「播报状态」,JS 负责「真的切换」:
<button aria-expanded="false" aria-controls="menu1">更多选项</button>
<ul id="menu1" hidden>...</ul>
点击时把 aria-expanded 改成 "true",同时移除 hidden。两者必须同步,只改一个读屏用户就会听到错误状态。
其他常用:aria-current="page" 标记当前导航项,aria-invalid="true" 标记校验失败的表单字段。
第五步:动态内容用 aria-live
表单提交结果、搜索无结果提示这类后来出现的内容,默认不会被播报。给它加:
<div role="status" aria-live="polite">已保存</div>
polite:等用户当前操作说完再播报,适合普通提示
assertive(或 role="alert"):立即打断,只用于错误和紧急信息
注意:aria-hidden="true" 千万不要加在可聚焦元素上(比如带 tabindex 的 div)。元素被隐藏了却还能 Tab 进去,会让键盘用户「焦点凭空消失」。
第六步:跑工具 + 手动键盘走一遍
自动化工具能查出一半问题,剩下的必须手动验证。
npx @axe-core/cli http://localhost:3000
或者打开 Chrome DevTools → Lighthouse → 勾选 Accessibility。浏览器插件推荐 axe DevTools 和 WAVE。
手动测试三件事:
- 拔掉鼠标,只用 Tab 键走完全流程,看焦点是否可见、顺序是否合理
- 检查是否有「跳转到主内容」的 skip link(放在
<body> 第一个可聚焦元素)
- Mac 按
Cmd + F5 开 VoiceOver,Windows 用 NVDA + Firefox,实际听一遍
小结
- 原生标签 > ARIA,
<button> 永远比 role="button" 靠谱
- 标题不跳级,
<main> 只有一个,地标区域多了要加 aria-label 区分
- 可访问名称优先级:
aria-labelledby > aria-label > 原生 > title
- 状态类 ARIA(
aria-expanded、aria-current)必须和 JS 同步更新
aria-live="polite" 给普通提示,assertive 只留给错误
- 工具只查一半,键盘 Tab 走一遍 + 屏幕阅读器实测才是终点