PAYATHON 2026

表单可访问性:使用 aria-hidden 还是从 DOM 移除?

支付小周

结论

当前不适用的表单字段可以从 DOM 中移除,这不会造成可访问性问题。但对大多数动态表单来说,更方便的做法是:

  • 用原生 hidden 属性隐藏整个条件区域;
  • 同时禁用区域内的表单控件;
  • 条件重新满足后,再取消隐藏和禁用。

不要只设置 aria-hidden="true"。这个属性只会在辅助技术的可访问性树中隐藏内容,无法阻止键盘聚焦,也不会阻止表单校验和数据提交。如果区域中还有可以聚焦的输入框,就可能出现键盘能够进入,但屏幕阅读器无法识别的严重问题。

推荐的实现方式

先用原生 HTML 表示用户组选择:

<fieldset>
  <legend>Select your user group</legend>

  <label>
    <input type="radio" name="userGroup" value="single" checked>
    Single
  </label>

  <label>
    <input type="radio" name="userGroup" value="families">
    Families
  </label>
</fieldset>

把仅适用于 Families 的字段放在单独的区域中:

<fieldset id="family-fields" hidden disabled>
  <legend>Family plan options</legend>

  <fieldset>
    <legend>Bill payment</legend>

    <label>
      <input type="radio" name="billPayment" value="combined">
      Combined
    </label>

    <label>
      <input type="radio" name="billPayment" value="separate">
      Separate
    </label>
  </fieldset>

  <fieldset>
    <legend>Data plan usage</legend>

    <label>
      <input type="radio" name="dataUsage" value="limited">
      Limited usage
    </label>

    <label>
      <input type="radio" name="dataUsage" value="shared">
      Shared usage
    </label>
  </fieldset>
</fieldset>

然后根据用户的选择同步更新状态:

const userGroupInputs = document.querySelectorAll(
  'input[name="userGroup"]'
);
const familyFields = document.querySelector('#family-fields');

function updateFamilyFields() {
  const selectedGroup = document.querySelector(
    'input[name="userGroup"]:checked'
  )?.value;

  const showFamilyFields = selectedGroup === 'families';

  familyFields.hidden = !showFamilyFields;
  familyFields.disabled = !showFamilyFields;
}

userGroupInputs.forEach((input) => {
  input.addEventListener('change', updateFamilyFields);
});

updateFamilyFields();

这两个属性各有作用:

  • hidden 隐藏整个区域,并使其通常不会出现在可访问性树中;
  • disabled 阻止内部控件获得焦点、参与约束校验或随表单提交。

仅仅隐藏控件,不一定能让它退出表单校验。因此,不适用的字段还需要禁用,尤其是其中包含 required 控件时。

是否可以从 DOM 中移除

可以。辅助技术只能访问当前存在于页面和可访问性树中的内容,所以移除不适用的字段本身不会引发可访问性问题。

例如,可以用 <template> 保存条件字段:

<div id="conditional-fields"></div>

<template id="family-fields-template">
  <fieldset>
    <legend>Family plan options</legend>

    <label>
      <input type="checkbox" name="combinedBilling">
      Use combined bill payment
    </label>

    <label>
      <input type="checkbox" name="limitedUsage">
      Apply limited data usage
    </label>
  </fieldset>
</template>
const container = document.querySelector('#conditional-fields');
const template = document.querySelector('#family-fields-template');

function renderConditionalFields(userGroup) {
  container.replaceChildren();

  if (userGroup === 'families') {
    container.append(template.content.cloneNode(true));
  }
}

这种方法适合字段结构差异较大的页面,也适合原本就使用组件框架进行条件渲染的项目。不过需要处理几个问题:

  • 节点移除后,用户已经填写的值通常会丢失;
  • 重新创建节点时,需要重新建立事件处理、错误提示等关联;
  • 不要直接移除当前持有焦点的元素。必要时,应先把焦点移到触发切换的控件或其他合适的位置;
  • 如果用户可能频繁切换选项,并希望保留已经填写的内容,隐藏并禁用会更合适。

因此,从 DOM 中移除字段在可访问性上没有问题,但维护成本不一定最低。

为什么不建议单独使用 aria-hidden

下面的写法有风险:

<div aria-hidden="true">
  <label>
    Bill payment
    <input type="text" name="billPayment">
  </label>
</div>

输入框仍可能获得键盘焦点,也可能参与校验和提交,但屏幕阅读器无法正常识别它。不要在包含可聚焦控件的区域上单独使用 aria-hidden="true"。

如果已经采用以下任意一种方式,通常不需要再添加 aria-hidden:

<section hidden>...</section>
.conditional-section {
  display: none;
}
element.remove();

这些方式本身就会让内容不再正常呈现给辅助技术。额外维护 aria-hidden,还可能造成状态不同步。

切换时还要注意什么

使用正确的表单语义

一组选项应使用同名的原生 radio,并通过 <fieldset> 和 <legend> 提供组名。不要用普通文本和可点击的 <div> 模拟单选按钮。

不要随意移动焦点

用户选择 Families 后,通常不用自动把焦点移到新增字段。焦点可以留在刚刚操作的 radio 上,让用户按照正常顺序继续浏览。

只有切换操作会移除当前焦点所在的区域时,才需要主动恢复焦点:

if (familyFields.contains(document.activeElement)) {
  document.querySelector(
    'input[name="userGroup"]:checked'
  )?.focus();
}

保持验证状态一致

隐藏区域中的字段不应继续触发 required 校验。使用 disabled 即可避免这个问题:

familyFields.disabled = familyFields.hidden;

重新启用字段时,还要确认此前留下的错误消息是否仍然适用。

动态内容不一定需要播报

如果新增字段紧跟在选择控件之后,用户通常可以按正常阅读顺序找到它们,不必强制使用 aria-live。只有页面其他位置发生了重要且不易察觉的变化时,才需要考虑添加简短的状态提示。过多的播报会干扰操作。

建议选择

这个表单优先采用“保留 DOM,使用 hidden 加 disabled 切换整个 <fieldset>”的方案。它的语义清楚,状态容易管理,也能避免隐藏字段继续获得焦点、参与校验或随表单提交。

如果项目使用 React、Vue 等框架,并且通常通过条件渲染控制字段,也可以直接从 DOM 中移除它们。处理好焦点、已输入的数据和错误状态即可。aria-hidden 不适合用作动态表单字段的主要隐藏机制。

备注:内容仅供参考。