标准 · ARIA

状态 动态区域状态

aria-live

将区域标记为其更新应由辅助技术宣读、而无需移动焦点的区域。大多数情况选用 "polite",仅在内容确实紧急时选用 "assertive"。该区域必须在初始渲染时就存在于 DOM 中。

使用时机

当页面某部分异步更新,且希望屏幕阅读器用户能知晓此更新、同时不打断其当前阅读时使用。表单验证摘要、搜索结果数量、Toast 通知、聊天流消息和购物车计数器是典型场景。

有意识地选择礼貌级别:

  • aria-live="polite"——等到用户空闲后再宣读。几乎所有场景都应使用此值。状态消息、加载完毕的结果、已添加至购物车的商品。
  • aria-live="assertive"——立即打断用户。仅用于真正紧急的信息——会话将在 30 秒后过期、表单提交失败、付款被拒绝。滥用会使页面令人反感。
  • aria-live="off"(默认值)——不进行任何宣读。

原生角色 role="status"(隐式 polite)和 role="alert"(隐式 assertive)已将 aria-live 与合理的默认值绑定在一起。适用时优先选用这两个角色;在自定义容器上不适用时再使用 aria-live

如何保持同步

关键规则:实时区域必须在初始渲染时就存在于 DOM 中。浏览器和辅助技术在节点首次出现在无障碍树时设置该区域的”监听”。如果你在同一个 JavaScript 执行周期内创建区域并注入内容,宣读往往会被错过。

正确模式:

<div id="status" aria-live="polite"></div>

在页面加载时渲染空容器,之后通过 JavaScript 向其写入文本,屏幕阅读器即宣读该变化。

其他规则:

  • 通过设置 textContent 来更新内容;替换整个区域的外层 HTML 可能会破坏监听。
  • 重复宣读需要内容变化——连续两次写入相同字符串通常不会产生第二次宣读。可添加计数器、时间戳,或短暂清空区域。
  • 多步骤更新期间配合 aria-busy="true" 以避免宣读不完整的内容。
  • 配合 aria-atomic 以控制宣读的是差异部分还是整个区域。

常见错误

  • 在与内容相同的执行周期内创建实时区域——不会触发宣读。
  • 所有情况都使用 aria-live="assertive"。用户会将该标签页静音。
  • 在可聚焦控件上设置 aria-live。实时区域用于状态更新,而非交互控件。
  • display: none 隐藏实时区域。CSS 隐藏的区域同样从无障碍树中隐藏,不会产生任何宣读;应使用视觉隐藏技术(clip / sr-only)代替。
  • 一次性将大量内容(整段文字)塞入实时区域——用户无法略读。
  • 消息被读取后忘记清空区域,导致后续相同内容的更新不产生任何宣读。

示例

<form>
  <label for="zip">邮政编码</label>
  <input id="zip" name="zip" />
  <button type="submit">查询</button>
</form>

<!-- 从初始渲染起始终存在 -->
<div id="lookup-status" aria-live="polite" class="sr-only"></div>

<script>
  document.querySelector('form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const status = document.getElementById('lookup-status');
    status.textContent = '正在查询位置……';
    const place = await lookup(document.getElementById('zip').value);
    status.textContent = `位置:${place}`;
  });
</script>