底部菜单必须用<button>或包裹图标文字,禁用纯;光晕需用:focus-visible重写并适配小屏;点击反馈依赖原生:active,禁用transform干扰;图标加fa-fw防晃动;真机不显光晕是系统合规策略,需role="tab"+aria-selected+手动focus同步状态。

sticky-bottom + button 是基础结构,不是 navbar
底部菜单必须用 <button> 或 <a> 包裹图标和文字,不能只放 <i>。否则点击区域小、无障碍失效、光晕(focus ring)不触发。Bootstrap 的 navbar 类默认用于顶部,语义上也不适配底部操作入口;直接复用会干扰 flex 布局和 focus 样式继承。
推荐结构:<div class="d-flex flex-column min-vh-100"><main>...</main><nav class="bottom-nav sticky-bottom"><ul class="list-group list-group-horizontal"><li class="list-group-item"><button type="button" class="btn btn-link p-0"><i class="fas fa-home fs-4 fa-fw"></i><span>首页</span></button></li></ul></nav></div>
光晕(focus ring)怎么让小屏也可见又不突兀
默认的 outline 在移动端常被系统忽略,或在 iOS 上仅显示为浅灰虚线,拇指点击时几乎不可见。必须显式重写 :focus-visible 样式,并限制仅在键盘导航时生效,避免鼠标点击也触发。
- 加 CSS:
.bottom-nav button:focus-visible { outline: 2px solid #0d6efd; outline-offset: 2px; border-radius: 12px; } - 禁用非必要 outline:
.bottom-nav button:focus:not(:focus-visible) { outline: none; } - 小屏下放大光晕尺寸:媒体查询里把
outline-width改成3px,outline-offset加到4px,确保拇指覆盖区域有明确反馈 - 别用
box-shadow模拟光晕——它不随元素圆角自适应,且在高对比度模式下可能消失
点击反馈(ripple / press effect)别用 JS 模拟
原生 <button> 在 Android Chrome 和 Safari 中自带 :active 状态压感反馈,但需满足两个条件:一是父容器不能有 pointer-events: none,二是不能用 transform: scale() 干扰层叠上下文。
- 确保按钮内无绝对定位子元素遮挡点击穿透
- 禁用默认按钮样式干扰:
.bottom-nav .btn-link { --bs-btn-active-bg: transparent; --bs-btn-active-border-color: transparent; } - 如需增强反馈,只改
:active的opacity(建议0.7),不要加 transition 或 transform——移动端 touchstart/touchend 事件间隙极短,CSS 动画容易卡顿或不触发 - Font Awesome 图标必须加
fa-fw,否则不同图标宽度差异会导致 :active 时文字轻微晃动,误判为交互异常
为什么真机测试时光晕偶尔不出现
这不是代码问题,是 iOS 16+ 和 Android 14 的 focus 策略变更:只有通过键盘 Tab 导航或辅助工具(如 VoiceOver)触发的 focus 才显示 :focus-visible;单纯触摸点击默认不触发。这是 WCAG 3.2.1 合规要求,不能绕过。
应对方式只有一条:给每个 <button> 加 role="tab" 并配 aria-selected="false",再用 JS 监听 click 后手动 .focus()。但注意——这会让屏幕阅读器重复播报“已选中”,所以必须同步管理 aria-selected 状态。复杂点在于:底部菜单通常是单页切换,得用 History API 或 URL hash 配合状态同步,否则刷新后焦点丢失、选中态错乱。


















