tabindex不是调顺序的工具,而是决定元素能否被Tab键聚焦及是否由脚本接管聚焦的开关;滥用正数(如全设为1)会破坏焦点流,导致键盘导航不可预测。

tabindex 不是用来“调顺序”的,而是决定一个元素“能不能被 Tab 键碰到”以及“该不该由脚本接管聚焦”的开关。滥用正数 tabindex(比如全设成 1)会让焦点跳转不可预测,尤其在选项卡这类动态组件里,键盘用户一按 Tab 就可能掉出组件、卡在空白处。
tabindex="0" 是让自定义 tab 按钮可聚焦的起点
原生 <button> 默认可聚焦,但用 <div role="tab"> 或 <span> 实现的选项卡按钮默认不在 Tab 流中。加 tabindex="0" 才能让它按 DOM 位置自然进入 Tab 序列。
但只加属性远远不够,必须同步做三件事:
- 补上
role="tab",否则屏幕阅读器读作“段落”或“无名容器”,不是“标签页” - 监听
keydown,对Enter和Space触发切换逻辑(注意Space要e.preventDefault(),否则页面滚动) - 提供视觉焦点样式,比如
:focus-visible { outline: 2px solid #007bff; },不然键盘用户根本看不到当前在哪
tabindex="-1" 是切换后把焦点移进面板的唯一可靠方式
点击 tab 按钮后,内容面板(role="tabpanel")显示出来,但焦点还停在按钮上。此时必须手动把焦点移到面板内首个可操作元素——比如面板里的第一个 <input> 或 <button>。
立即学习“前端免费学习笔记(深入)”;
这个目标元素得提前设 tabindex="-1",否则 .focus() 无效。常见错误包括:
- 给整个
tabpanel容器设tabindex="-1",结果焦点落在空容器上,用户按 Tab 还是跳到页面其他地方 - 没检查目标元素是否已渲染完成(比如异步加载的表单),
.focus()调用时 DOM 还不存在 - 聚焦后元素被 CSS 遮挡或溢出隐藏,没配
element.scrollIntoView({ block: 'nearest' }),用户看不见焦点在哪
绝对不要用 tabindex="1" 控制 tab 切换顺序
有人给所有 tab 按钮设 tabindex="1",以为能统一“先切 tab 再进内容”。实际效果是:这些按钮全挤在 Tab 流最前面,彼此顺序仍按 HTML 出现位置走,和没设一样;更糟的是,一旦混入 tabindex="0" 的普通链接或表单,焦点顺序就变成“正数 → 0 值 → 其他”,完全失控。
真实 Tab 导航依赖两层结构:
- Tab 键本身只在
tabindex >= 0的元素间线性流转 - 箭头键(
ArrowLeft/ArrowRight)、Home/End的循环切换,必须靠 JS 拦截并手动管理焦点,tabindex对它完全无效
也就是说,tabindex 管不了方向键行为,只管“Tab 键从哪开始、到哪结束”。指望它实现完整的选项卡键盘导航,注定失败。
移动端 Safari 会无视大部分 tabindex="0"
iOS Safari 默认禁用非表单元素的 Tab 导航:即使写了 tabindex="0",<div role="tab"> 依然无法被 Tab 键选中。它只认 <input>、<button>、<select>、<textarea> 和带 contenteditable 的元素。
这意味着:
- 纯靠
tabindex实现的选项卡,在 iPhone 上键盘用户根本无法用 Tab 键切换 - 如果必须支持,得降级为用原生
<button>+role="tab",或者放弃 Tab 键,只保方向键逻辑(但需明确告知用户) - 测试时务必真机验证,模拟器和 Chrome DevTools 的移动端预览不反映真实行为
最易被忽略的一点:焦点管理不是“设完 tabindex 就完事”,而是每次 DOM 变化(显示/隐藏/挂载/卸载)后,都得重新确认焦点在哪、该移到哪、是否可见——漏一次,键盘导航就断链。



















