Skip to content
返回质量保障

屏幕阅读器支持

通过提供语义 HTML 和 ARIA 确保应用用屏幕阅读器(NVDA、JAWS、VoiceOver、TalkBack)可用。

为什么

屏幕阅读器是盲或低视力用户访问应用的方式——它朗读页(或输出到盲文显示)并按语义结构(标题、区域、表单、链接)导航。无语义 HTML 和 ARIA,屏幕阅读器见一堆无意义 div: 不能宣按钮为按钮、表单错误为错误、购物车更新为变更。用户迷。语义 HTML(button、nav、main、h1-h6、label、fieldset)免费提供意义,ARIA(aria-label、aria-live、aria-expanded、role)填自定义 widget 缺口。支持屏幕阅读器非小众——是仅美国 700 万盲用户访问 web 的方式,是 ADA 下法律要求。

何时使用

每个面向用户功能。自定义 widget 开发时用屏幕阅读器测,发布前全过。动态内容或自定义交互变时重测。

如何做

1. 先用语义 HTML——是基础
   - <button>、<a>、<input> 无任何 ARIA 正确宣读
   - <nav>、<main>、<aside>、<footer> 创可导航区域
   - <h1> 到 <h6> 创用户可按导航的标题大纲(H 键)
   - <label> 关联 <input> 使字段带标签宣读
   - <fieldset> + <legend> 分组相关输入(单选按钮、复选框)

2. 仅语义 HTML 不足时加 ARIA
   - aria-label: 无可见文本时提供标签(仅图标按钮)
   - aria-labelledby: 指向标此元素的可见元素
   - aria-describedby: 指向帮助文本或错误消息
   - aria-live="polite": 宣动态变更(购物车更新、toast、搜索结果)
   - aria-expanded: 指示下拉或手风琴开或关
   - role: 仅必要时覆盖隐式 role(罕见——优先语义 HTML)

3. 用实际屏幕阅读器测
   - NVDA(免费)+ Windows 上 Chrome 或 Firefox——最常见组合
   - Mac 上 VoiceOver + Safari(Cmd+F5 切换)
   - Android 上 TalkBack(设置 → 无障碍)
   - 导航: H(标题)、F(表单)、K(链接)、R(区域)、Tab(交互)

4. 验证宣读质量
   - 每交互元素宣其 role 和标签吗?("提交,按钮")
   - 表单错误宣读吗?(aria-describedby 指错误或 aria-live)
   - 动态变更宣读吗?(购物车 aria-live="polite"、错误 "assertive")
   - 标题结构合理吗?(一个 h1、然后 h2、无跳级)

5. 处理常见模式
   - 仅图标按钮: <button aria-label="关菜单"><XIcon /></button>
   - 加载状态: <div role="status" aria-live="polite">加载中...</div>
   - 错误消息: <div role="alert">{error}</div>(隐式 aria-live="assertive")
   - 标签 widget: 用 role="tab"、role="tablist"、role="tabpanel"、aria-selected、aria-controls

示例

// 语义 HTML: 带标签和错误的表单
<form>
  <fieldset>
    <legend>送货地址</legend>
    <div>
      <label htmlFor="street">街道地址</label>
      <input id="street" type="text" aria-describedby="street-error" required />
      <div id="street-error" role="alert">{errors.street}</div>
    </div>
  </fieldset>
  <button type="submit">下单</button>
</form>
// 屏幕阅读器: "街道地址,编辑,必填。[错误] 街道必填。"

// 仅图标按钮: 提供无障碍名
<button aria-label="关对话框" onClick={close}>
  <XIcon aria-hidden="true" />
</button>
// 屏幕阅读器: "关对话框,按钮"

// 动态内容: 宣购物车更新
function AddToCartButton() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>加购物车</button>
      <div role="status" aria-live="polite">
        购物车中 {count} 件
      </div>
    </div>
  );
}
// 屏幕阅读器: "加购物车,按钮。购物车中 0 件。"
// 点击后: "购物车中 1 件。"(自动宣读)

// 自定义标签 widget: 用 ARIA role 和属性
<div role="tablist">
  <button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">详情</button>
  <button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2">评论</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">...</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>...</div>

// NVDA 测: 按 H 按标题导航、F 按表单、Tab 过交互
// 验证: 每元素宣其 role、标签和状态

常见问题

  • ⚠给 <div onClick> 加 role="button" 而非用 <button>——div 现宣为按钮但仍不可键盘聚焦或激活,屏幕阅读器用户知是按钮但不能用。
  • ⚠用重复可见文本的 aria-label——屏幕阅读器宣"关,关"因 aria-label 覆盖并重复文本,使输出比无 ARIA 更坏。
  • ⚠动态内容忘 aria-live——购物车数视觉更新但屏幕阅读器用户永不被告诉,他们以为按钮没工作。
  • ⚠为样式用标题级(小字体用 h2、粗体用 h3)而非结构——标题大纲无意义,屏幕阅读器用户不能按导航。

最佳实践

  • ✓以语义 HTML(button、nav、main、h1-h6、label、fieldset)为基础——免费提供 role、标签和键盘支持,ARIA 仅自定义 widget 需。
  • ✓至少用两个屏幕阅读器(NVDA + VoiceOver)真实浏览器测——不同屏幕阅读器不同解 ARIA,测两者抓不一致。
  • ✓非紧急更新(购物车、搜索结果)用 aria-live="polite"、错误用 role="alert"(隐式 assertive)——礼貌级决定宣读是否打断用户。
  • ✓维护合理标题结构(一个 h1、然后 h2、无跳级)——标题大纲是屏幕阅读器用户主要导航方法,坏结构使页不可导航。