必须用<ol>包裹步骤项并为当前步设aria-current="step",禁用非当前步用aria-disabled="true",CSS通过属性选择器驱动状态样式,连接线用::after实现,键盘交互需支持Tab/Enter/方向键且焦点同步。

步骤指示器的 HTML 结构必须支持语义化与可访问性
纯视觉的步骤条(比如一堆 <div> 套 <span>)在屏幕阅读器下无法被正确识别,且无法键盘导航。正确的起点是用 <ol> 包裹步骤项,每项用 <li>,并为当前步骤添加 aria-current="step"。
常见错误是把步骤写成无序列表或纯 div 流,导致 tabindex 失效、焦点丢失、SR 朗读为“列表项 1”而非“第 1 步:填写基本信息”。
- 每个
<li>内部应包含一个<button>或带tabindex="0"的<span>,确保可聚焦 - 禁用非当前步骤时,用
disabled属性(对<button>)或aria-disabled="true"(对非表单元素) - 步骤标题文本必须是实际文本节点,不能仅靠
aria-label补充
用 CSS 控制步骤状态比 JS 操作 class 更可靠
很多人习惯用 JS 动态增删 active、completed 等 class,但状态同步容易出错——比如异步校验未完成就跳转,class 已变但数据还没提交。更稳的方式是让 CSS 根据属性选择器驱动样式:
ol.steps > li[aria-current="step"] .step-indicator { background: #007bff; }
ol.steps > li[aria-current="step"] .step-label { font-weight: 600; }
ol.steps > li[aria-disabled="true"] .step-indicator { opacity: 0.4; }
这样只要 JS 正确更新 aria-current 和 aria-disabled,视觉状态就自动同步,无需维护两套状态映射逻辑。
立即学习“前端免费学习笔记(深入)”;
- 避免用
:nth-child()做状态样式,它和语义无关,易被 DOM 变动破坏 - 连接线(progress line)建议用伪元素
::after实现,而非额外 DOM 节点,减少 JS 操控负担 - 移动端需注意
touch-action: manipulation防止点击延迟
状态切换必须绑定到明确的用户动作,而非路由或定时器
步骤前进/后退不该由 window.location.hash 变化或 setTimeout 自动触发——这会让用户失去控制权,也违背多步骤表单的核心约束:上一步校验通过才能进入下一步。
- 前进操作应绑定在「下一步」按钮的
click事件,并在回调中先执行校验(如form.checkValidity()),再调用状态更新函数 - 后退按钮只更新
aria-current和aria-disabled,不重置表单字段值(除非用户主动点击「重新开始」) - 禁止监听
popstate自动回退步骤——浏览器后退键应由组件自身接管,否则会跳过中间校验 - 若步骤涉及异步提交(如保存草稿),状态切换必须等 Promise resolve 后再进行,否则用户可能看到「已完成」但数据其实没存上
键盘交互支持不能只靠 tabindex
仅给步骤项加 tabindex="0" 不够。用户按 Tab 键应能顺序聚焦所有可操作步骤,但 Enter/Space 还需触发跳转,方向键(→/←)应在水平布局中切换步骤,Home/End 应跳首尾。
- 监听
keydown时,对ArrowRight/ArrowLeft要阻止默认行为(避免页面滚动) - 步骤跳转后,新步骤项必须
focus(),否则键盘用户不知道焦点在哪 - 禁用状态的步骤仍需保留在 tab 顺序中(用
tabindex="0"+aria-disabled),否则 tab 键会跳过它造成体验断层 - 不要用
role="tablist"模拟步骤条——它隐含 ARIA Tab Pattern 的全套行为(如自动焦点管理),与步骤流程不匹配,反而增加复杂度



















