要手动添加role和aria-*属性——Bootstrap不自动注入,交互组件的无障碍状态需自行补全,包括aria-expanded、aria-modal、aria-hidden、aria-live等,并同步更新动态状态。

Bootstrap 的 role 和 aria-* 属性要不要手动加?
要加,而且必须加——Bootstrap 本身不自动注入 role 或 aria- 属性,它只提供基础语义结构(比如 <button>、<nav>),但交互组件(下拉菜单、模态框、轮播图)的无障碍状态全靠你补全。
常见漏掉的场景:点击展开的折叠面板没加 aria-expanded,关闭后没同步更新;模态框没设 aria-modal="true" 或缺失 aria-labelledby;自定义 checkbox/radio 没用 aria-checked 反映真实状态。
- 所有动态显示/隐藏的内容,必须用
aria-hidden="true/false"控制是否被读屏器忽略 -
role="alert"或role="status"要配aria-live(值推荐"polite"),否则表单验证提示不会被朗读 - 禁用状态的按钮记得同时加
disabled属性和aria-disabled="true"(部分旧读屏器只认后者)
Bootstrap 的 focus 管理为什么经常失效?
因为 Bootstrap 默认不接管键盘焦点流——比如模态框打开后,焦点没自动落到第一个可聚焦元素,关闭后也没回退到触发按钮,这直接导致键盘用户卡死。
必须手动处理:Modal 实例的 show 和 hidden 事件里写逻辑;Dropdown 需监听 shown.bs.dropdown 并调用 .focus();自定义 tab 组件得用 tabindex="-1" + .focus() 手动引导。
- 模态框打开时:用
$('#myModal').on('shown.bs.modal', () => $('#myModal .form-control:first').focus()) - 模态框关闭后:保存触发元素引用,在
hidden.bs.modal中调用triggerEl.focus() - 避免用
display: none隐藏内容——它会彻底移出可聚焦流,改用visibility: hidden+position: absolute+clip-path组合隐藏但保留焦点能力
颜色对比度和 focus-outline 怎么不破坏 Bootstrap 主题?
Bootstrap 默认的 focus-outline 是浅蓝虚线,很多主题覆盖后变细或消失,导致键盘用户完全找不到当前焦点位置;文字与背景对比度也常低于 WCAG AA 标准(4.5:1)。
别直接删 :focus 样式,而是用 box-shadow 增强轮廓,或改用 outline-offset 微调位置;颜色对比度检查不能只看设计稿,要用浏览器插件(如 axe DevTools)实测渲染后的像素比。
- 增强 focus 样式示例:
button:focus { outline: 2px solid #0d6efd; outline-offset: 2px; } - 深色主题下,
$body-color和$body-bg必须重定义并验证对比度,比如$body-color: #e9ecef配$body-bg: #212529是合规的 - 图标按钮必须配
aria-label,不能只靠title属性——后者在多数读屏器中默认不朗读
Bootstrap 表单控件的 label 关联为什么总出问题?
核心问题在于:用 form-control 类但没写 for/id 匹配,或用了 floating-label 却没确保 <label> 包裹 <input> —— 这会导致读屏器无法把标签和控件关联,用户不知道该填什么。
浮动标签(Floating Labels)尤其危险:它依赖 CSS 动画“上浮”,但初始状态若 input 有值或 focused,label 可能永远不显示,又没其他文本提示。
- 必做:
<label for="email">邮箱</label><input id="email" class="form-control"> - 浮动标签必须配合
<div class="form-floating">容器,且label必须写在input后面(顺序错就失效) - 多选/单选组用
<fieldset>+<legend>,别只靠视觉分组——这是读屏器识别“一组相关选项”的唯一依据
无障碍不是加几个属性就完事,关键是状态同步:每次 DOM 变更(显隐、启用/禁用、值更新),对应的 ARIA 属性必须实时响应。最常被忽略的是异步操作——比如 AJAX 提交后弹 toast,若没加 role="status" 和 aria-live,用户根本不知道成功了还是失败了。


















