单页应用多步表单无障碍实现:单<form>vs多<form>方案及相关问题
核心问题:单<form>加内部显隐的方案是否有天然无障碍缺陷
只要正确处理显隐逻辑,这个方案不仅没有天然无障碍问题,反而比多<form>的方案更贴合原生表单语义。
很多人会担心屏幕阅读器会读到隐藏步骤的内容,实际上只要在非当前显示的步骤块上加原生hidden属性,浏览器和所有主流辅助技术都会自动跳过这些块内的所有元素,既不会被朗读、也不会被聚焦,完全不会干扰当前步骤的输入体验。
反而如果用多独立<form>的结构,辅助技术会把每个<form>识别为一个完全独立的表单模块,容易让用户误以为已经跳转到了全新的页面,和单页应用内的步骤切换体验相悖。
方案1(单<form>结构)的子问题:是否需要用<fieldset>划分步骤
必须用,这是成本最低、语义最准确的实现方式。
- 原生
<fieldset>标签的设计初衷就是给同组关联的表单字段做语义分组,搭配<legend>标签写当前步骤的标题(比如“第一步:填写基础账号信息”),屏幕阅读器在用户导航到表单区域时,会直接朗读分组标题,不需要额外补ARIA属性就能让用户明确知道当前正在填写的字段属于哪个步骤。 - 替换原来普通
<div>做步骤容器的结构后,能省去大量为了做分组语义写的自定义ARIA代码,降低出错概率。
正确的基础结构参考:
<form> <!-- 当前显示的第一步 --> <fieldset id="step-1"> <legend>第一步:填写账号基础信息</legend> <!-- 第一步对应的所有输入控件 --> </fieldset> <!-- 隐藏的第二步,用hidden属性控制显隐 --> <fieldset id="step-2" hidden> <legend>第二步:补充收货地址信息</legend> <!-- 第二步对应的所有输入控件 --> </fieldset> </form>
注意:不要只用CSS的
display:none替代hidden属性做显隐控制,部分老旧的辅助技术对纯CSS隐藏的元素识别存在兼容问题;hidden是原生语义的隐藏标记,兼容度最高,还会自动让块内的所有元素从键盘焦点序列中移除,不会出现用户按Tab键跳到看不见的输入框的问题。
方案2(多独立<form>结构)的子问题:是否需要告知用户当前处于同一份流程中
不仅需要,还要针对性补全大量语义逻辑,不然会出现明显的无障碍体验断层。
因为每个独立<form>标签对辅助技术来说,都是一个独立的表单交互模块,用户在导航过程中会自然认为这是两个没有关联的独立表单,根本意识不到自己还在同一条多步流程里。需要做的适配包括:
- 给每个独立的
<form>加aria-describedby属性,关联一段公共的流程说明文本,比如“当前为下单流程的第2步/共3步,上一步填写的信息将自动保留”,让屏幕阅读器在进入表单时先朗读流程说明。 - 每次切换步骤、显示新的表单块时,主动把焦点移到当前步骤的标题位置,同时通过ARIA live区域主动播报步骤进度,比如“已进入第二步:补充收货地址”,避免用户在焦点跳转过程中迷路。
- 关闭非当前显示表单的原生HTML校验逻辑,防止隐藏的表单在用户操作过程中突然弹出校验提示,打断用户的输入流程。
两种方案的选型建议
优先选择单<form>加<fieldset>分组、hidden控制显隐的实现方案,这套方案的原生语义足够覆盖绝大多数无障碍场景,开发成本最低、兼容度最高。
多独立<form>的方案需要额外补大量自定义ARIA和焦点控制逻辑,才能达到和单<form>方案差不多的体验,整体开发成本更高,容易出兼容问题。
内容的提问来源于stack exchange,提问作者alaboudi

