应使用语义化 nav + aria-current 实现步骤条:用 包裹带 href 锚点的 ,状态仅通过 aria-current="step"(当前步)或 aria-current="true"(已完成)标记,图标由 ::before 伪元素按属性驱动生成,连线用 border-right + flex-grow 动态撑开,切换前必须同步校验并聚焦首个无效字段。

用 nav + aria-current 搭语义化步骤结构,别碰 .progress
Bootstrap 官方没有 steps、.step-bar 或任何内置分步表单组件。所谓“带图标和进度变化”的响应式步骤条,必须靠语义化 HTML + 属性驱动 CSS + 同步 JS 实现。硬套 .progress 组件会破坏键盘导航、读屏识别和步骤状态一致性——它只表达“完成了 60%”,不表达“当前在第 2 步,且第 1 步已校验通过”。
正确结构是:<nav aria-label="表单步骤"><ol class="nav nav-pills"><li class="nav-item">@#@#@#@#@#@#@#@#@#@0</li></ol></nav>。每步 <a></a> 必须有 href 指向对应表单区域 ID(如 #step-2),否则浏览器前进/后退、锚点跳转全部失效。
状态标记只认 aria-current="step"(当前进行中)或 aria-current="true"(已完成)。别用 class="active"——它对屏幕阅读器不可见,也不参与状态绑定;服务端渲染时,这个属性必须由后端准确输出,否则 React/Vue hydrate 会错位。
图标用 ::before 伪元素生成,状态由属性选择器控制
图标不能插额外 DOM 元素(比如在 <a></a> 里写 <i class="bi bi-check"></i>),否则小屏折行时易错位、缩放后基线偏移、无障碍语义污染。统一用 ::before 生成:
.nav-link::before {
content: "";
display: inline-block;
width: 1.25rem;
height: 1.25rem;
border-radius: 50%;
margin-right: 0.5rem;
vertical-align: middle;
}
样式由属性驱动:
-
[aria-current="step"]::before设主色圆点(如background-color: #0d6efd) -
[aria-current="true"]::before设完成图标(如content: "✓"; color: #198754) -
:not([aria-current])::before设未激活灰阶(如background-color: #adb5bd)
注意:不要混用 "true" 和 "step";移动端字体建议设为 font-size: 0.875rem,防止 nav-link 高度撑开导致图标垂直偏移。
连线用 border-right + flex-grow: 1,别写死宽度或用 transform
连接线本质是进度指示的一部分,不是装饰。横排时,给非末项的 .nav-link 加 border-right: 2px solid #e9ecef,再配合 flex-grow: 1 动态撑开,比 transform: scaleX() 或内联 style.width 更稳——缩放、断点、文字折行都不卡顿、不偏移。
关键细节:
- 只在非最后一项画线:
.nav-item:not(:last-child) .nav-link,避免底部多一条线 - 竖排响应式(
@media (max-width: 576px))时,移除border-right,改用border-bottom并重置gap: 1rem和padding: 0.75rem 1rem - 别依赖
gap-3这类工具类——在flex-column下它们的间距行为不稳定
绝对定位或 ::after 硬画线,在小屏文字折行后极易错位,尤其当某步标题过长时。
切换前必须同步校验,禁用逻辑不能只靠 CSS
用户点“下一步”不能只改 aria-current 或加个 class,否则会出现“UI 走到第 3 步,但第 2 步邮箱字段根本没填”的逻辑断裂。真实流程必须在点击事件里调用 checkStepValidity(),而不是等最终 submit 才校验。
具体操作:
- 校验失败时,用
input.focus()定位到第一个无效字段,别只弹alert - 禁用“下一步”按钮必须用
nextBtn.disabled = true,而不是只加opacity: 0.5—— 后者仍可被键盘聚焦、空格触发 - 若某步含异步校验(如用户名可用性检查),按钮需进 loading 状态,并设
disabled防重复提交 - 禁用未激活步骤项时,用
tabindex="-1"+pointer-events: none,而非disabled—— 后者会砍掉键盘焦点,破坏可访问性
最易被忽略的是:服务端输出的 aria-current 必须与前端 JS 状态严格一致;否则表单回填、错误重定向后,进度指示和实际表单状态就脱钩了。











