用 aria-live 与 aria-describedby 实现异步表单校验错误播报

举报
yd_217513249 发表于 2026/10/31 23:21:06 2026/10/31
【摘要】 异步表单校验常出现错误提示已显示但读屏器未感知、重复提交重复播报的问题。本文用 role=alert 汇总错误、aria-describedby 关联字段错误,并在聚焦首个错误字段时通过消息去重避免重复播报,附最小可运行示例。

用 aria-live 与 aria-describedby 实现异步表单校验错误播报

问题场景

在注册、结算等表单中,校验常放在提交后异步完成。若错误提示只是把红色文字插入页面,屏幕阅读器用户可能不知道提交失败;若每个字段错误都使用独立的 live region,又可能连续打断播报。更常见的问题是:同一份错误连续提交两次,提示区域被重复写入,读屏器再次播报,造成干扰。

目标可以拆成四点:字段错误与输入框建立稳定的描述关系;提交后通过一个 role=alert 区域播报汇总;把焦点移到首个错误字段;错误集合没有变化时,不重复更新播报区域。

设计要点

  • 表单使用 novalidate,由脚本统一接管校验和错误展示。
  • 每个输入框用 aria-describedby 指向自己的错误文本节点。错误文本出现时再移除 hidden,并给输入框设置 aria-invalid='true';修正后移除该属性。
  • 页面只保留一个 role='alert' 的汇总区域。role=alert 隐含 assertive 播报,代码中仍写出 aria-live='assertive' 和 aria-atomic='true',便于阅读意图。
  • 异步结果返回后,先写入字段错误,再聚焦首个错误字段。这样聚焦时 aria-describedby 指向的错误文本已经可见。
  • 用 lastAnnouncement 记录上一次写入的播报文本。只有文本变化时才修改 live region;连续提交相同错误不重复写 DOM。

最小可运行示例

保存为一个 HTML 文件即可运行。异步校验用 setTimeout 模拟,真实项目中替换为 fetch 或接口 SDK,并保留同样的错误对象结构。

<!doctype html>
<html lang='zh-CN'>
<head>
  <meta charset='utf-8'>
  <title>异步表单校验无障碍示例</title>
</head>
<body>
  <form id='signup' novalidate>
    <p>
      <label for='name'>姓名</label><br>
      <input id='name' name='name' autocomplete='name'
             aria-describedby='name-error'>
      <span id='name-error' hidden></span>
    </p>

    <p>
      <label for='email'>邮箱</label><br>
      <input id='email' name='email' type='email' autocomplete='email'
             aria-describedby='email-error'>
      <span id='email-error' hidden></span>
    </p>

    <button type='submit'>提交</button>

    <div id='form-status'
         role='alert'
         aria-live='assertive'
         aria-atomic='true'></div>
  </form>

  <script>
    const form = document.getElementById('signup');
    const statusEl = document.getElementById('form-status');
    let lastAnnouncement = '';

    function setFieldError(input, message) {
      const errorEl = document.getElementById(
        input.getAttribute('aria-describedby')
      );

      if (message) {
        errorEl.textContent = message;
        errorEl.hidden = false;
        input.setAttribute('aria-invalid', 'true');
      } else {
        errorEl.textContent = '';
        errorEl.hidden = true;
        input.removeAttribute('aria-invalid');
      }
    }

    // 模拟异步校验。真实项目替换此函数,返回 { 字段名: 错误文本 }。
    function validate(values) {
      return new Promise((resolve) => {
        setTimeout(() => {
          const errors = {};

          if (!values.name.trim()) {
            errors.name = '请输入姓名';
          }

          if (!values.email.trim()) {
            errors.email = '请输入邮箱';
          } else if (!values.email.includes('@')) {
            errors.email = '邮箱格式不正确';
          }

          resolve(errors);
        }, 400);
      });
    }

    form.addEventListener('submit', async (event) => {
      event.preventDefault();

      const inputs = Array.from(form.querySelectorAll('input'));

      // 清空上一轮字段错误;不要清空 statusEl,避免额外播报。
      inputs.forEach((input) => setFieldError(input, ''));

      const values = Object.fromEntries(new FormData(form).entries());
      const errors = await validate(values);

      inputs.forEach((input) => {
        setFieldError(input, errors[input.name] || '');
      });

      const firstInvalid = inputs.find((input) => errors[input.name]);
      const summary = firstInvalid
        ? '表单校验未通过:' + Object.values(errors).join(';')
        : '提交成功';

      // 相同播报文本不重复写入,避免连续提交同一错误时反复播报。
      if (summary !== lastAnnouncement) {
        statusEl.textContent = summary;
        lastAnnouncement = summary;
      }

      if (firstInvalid) {
        // 先展示错误,再聚焦,确保描述文本可被读取。
        firstInvalid.focus();
      }
    });
  </script>
</body>
</html>

运行与验证

把示例保存为 accessible-form.html,用浏览器打开。直接按 Tab 找到提交按钮并回车,或清空字段后提交,会看到焦点落到首个错误输入框。使用支持 ARIA 的读屏器测试时,重点检查三点:错误文本是否在聚焦时被读出;汇总区域是否只在错误集合变化时播报;修正字段后再次提交,旧错误是否消失。

真实接入时,把 validate 中的 setTimeout 换成自己的异步请求,并保持返回结构为字段名到错误文本的映射。若接口返回错误列表,可以先转换成该结构,再复用 setFieldError 和播报逻辑。

边界与限制

  1. role=alert 是 assertive 播报,适合提交失败、提交成功这类重要状态,不适合输入过程中的每次提示。输入过程中的提示可改用 aria-live='polite',或只更新字段级描述。
  2. 不同读屏器对 alert 与聚焦时 aria-describedby 的处理不同,可能出现汇总与字段错误各读一次。若重复感明显,可把汇总缩短为「表单有 2 个错误」,具体错误交给字段描述;是否需要保留完整错误列表,应结合测试决定。
  3. 示例中的邮箱检查只判断 @,是占位逻辑,不能替代服务端校验。真实规则应返回明确的字段错误,不要把多个错误拼在一个字符串里。
  4. 快速连续提交可能造成异步结果乱序。生产代码应禁用提交按钮、增加请求序号,或只接受最后一次请求的结果。
  5. aria-describedby 可以指向多个 id,例如提示文本和错误文本同时存在时写成两个 id;不要用颜色作为唯一错误提示,也不要移除输入框的焦点轮廓。

按照上述结构,字段错误有可见文本和描述关系,汇总播报受去重控制,焦点顺序也符合键盘用户预期。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。