应使用 nav 包裹 ol,每步为 li>a,通过 aria-current="step" 标识当前步、aria-current="true" 标识已完成步;伪元素绘制圆点与连线;禁用项用 tabindex="-1"+pointer-events:none+opacity:.5;表单需确保 form-floating 结构严格合规且同步焦点与校验。

用 nav + ol + aria-current 搭语义化进度条
Bootstrap 5 没有 .steps 或 .progress-stepper 这类内置类,硬套 .progress 会卡在“只能线性、不能点击、状态不可读”上。真正可维护的进度指示器必须是导航控件,不是数值反馈器——所以得用 nav 包 ol,每步用 li + a,靠 aria-current 驱动状态。
关键点:
-
aria-current="step"表示当前进行中(屏幕阅读器可识别,且 Bootstrap 5 官方推荐) -
aria-current="true"表示已完成(仅当需视觉区分“完成”和“进行中”时才用) - 别用
class="active"——它对辅助技术无效,JS 切换后也无法被 AT 解析 - 每个
a的href应指向对应表单锚点(如#step-2),否则浏览器前进/后退会断连
用伪元素 ::before 和 ::after 画图标与连线
图标不能用 img 或绝对定位塞进去,否则小屏下错位、文字折行时基线偏移、缩放后模糊。稳妥做法是统一用伪元素生成圆点和连接线。
核心 CSS 片段:
立即学习“前端免费学习笔记(深入)”;
nav .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 { background-color: #198754; }
:not([aria-current])::before { background-color: #adb5bd; }
.nav-item:not(:last-child) .nav-link::after {
content: "";
display: inline-block;
height: 0.125rem;
flex-grow: 1;
background-color: #adb5bd;
margin-left: 0.5rem;
margin-right: 0.5rem;
vertical-align: middle;
}
注意:
- 尺寸用
rem,不写死px,确保缩放和响应式一致 - 连接线只在非末项画,靠
:not(:last-child)控制,避免最后多一根线 - 移动端竖排时,用媒体查询把
::after改成border-bottom,并重置flex-direction和gap
禁用未激活项时,别用 disabled
disabled 会直接砍掉键盘焦点和屏幕阅读器访问路径,导致无法用 Tab 键跳转、无法用语音命令操作。正确做法是用 tabindex="-1" + pointer-events: none + opacity: 0.5 组合。
例如:
[aria-current="false"], :not([aria-current]) {
pointer-events: none;
tabindex: -1;
opacity: 0.5;
}
这样既视觉灰化,又保留语义结构,AT 能读到但用户无法误点,Tab 键也自然跳过。
同步表单步骤显隐时,form-floating 结构容易崩
每个步骤里若用 form-floating,必须保证:div.form-floating > input + label[for="xxx"] 严格相邻,且所有 input 的 id 全局唯一。常见崩坏场景:
- 多个步骤都用了
id="email"→ 浮动标签失效,聚焦后 label 不上浮 -
label和input中间插了span或空格 →form-floating的 JS 校验逻辑直接退出 - 某步骤被
display: none后再切回来,父容器宽度重算,form-control缺失w-100→ 输入框突然变窄甚至溢出
解决办法:所有 input/select/textarea 必须加 class="form-control w-100";form-floating 内部虽设了 width: 100%,但前提是结构零误差。
最易被忽略的是状态同步时机:JS 切换步骤后,必须立刻更新 aria-current、滚动到对应锚点、触发 focus() 到首个可聚焦元素,并重置表单校验状态——漏掉任一环,都会让键盘用户或屏幕阅读器用户卡在“看不见的步骤”里。


















