tabindex 用于控制元素是否可被 Tab 键聚焦,非排序工具;tabindex="0" 仅适用于需手动加入 Tab 流的非原生可聚焦元素,并须配 role、键盘事件监听及焦点样式;tabindex="-1" 专用于 JS 动态聚焦;正整数 tabindex 应禁用。

tabindex 不是用来“调顺序”的,而是决定一个元素“该不该被 Tab 键碰到”——用错值(尤其是正整数)会直接破坏键盘用户的导航逻辑,不是优化,是制造障碍。
tabindex="0" 是让自定义元素进 Tab 流的唯一合理方式
原生可聚焦元素(<button>、<a href>、<input>)默认就在 Tab 流里,加 tabindex="0" 多余,还可能干扰 SSR/hydration 一致性。只有 <div>、<span> 这类非交互语义元素需要显式加入时,才用 tabindex="0"。
- 必须同步加
role属性,比如role="button",否则屏幕阅读器不知道这是可操作项 - 必须监听
keydown,对Enter和Space都做响应;Space要event.preventDefault(),否则页面会滚动 - 必须提供
:focus-visible或至少:focus样式,否则键盘用户看不到焦点在哪 - 别给纯装饰性元素(如图标
<svg>容器)加tabindex="0",这会让辅助技术多读一遍无意义内容
tabindex="-1" 是动态组件中接管焦点的唯一安全通道
模态框、折叠面板、下拉菜单打开后,焦点不能靠 DOM 顺序“等”,必须由 JS 主动落到目标元素上——而那个目标元素得先设 tabindex="-1",否则 .focus() 无效或被忽略。
- 模态框打开后,立即对第一个可操作项(如“确认”按钮)调用
.focus(),它需有tabindex="-1" - Accordion 展开后,焦点应移到新暴露的首项(如内部链接),不是留在触发标题上
- 关闭弹层后,焦点应回退到触发它的元素(如“编辑”按钮),可用
document.activeElement记录或传参保存 -
tabindex="-1"不等于视觉隐藏:仅设它,但用opacity: 0或position: absolute; left: -9999px隐藏的元素,仍可能被 Tab 到,需配合inert或显式移除tabindex
正整数 tabindex(如 "1", "2")在任何场景下都该禁用
浏览器对正整数的处理是:所有 > 0 的值优先于 tabindex="0" 和无 tabindex 元素,再按数值升序排列;但多个相同值(如全写 tabindex="1")时,仍按 DOM 顺序走——你以为排好了,其实没排。
立即学习“前端免费学习笔记(深入)”;
- SSR 渲染时,服务端生成的顺序和客户端 hydration 后的
tabindex值极易不一致,引发焦点错位警告 - 组件复用或动态插入新元素后,正整数必须全局重算,几乎不可维护
- 屏幕阅读器用户依赖文档流理解上下文,强行把页脚按钮塞到第一个位置,等于剥夺其结构感知能力
-
aria-hidden="true"不影响tabindex行为,若同时存在,必须显式设tabindex="-1",否则可能意外被 Tab 到
隐藏/禁用元素的 tabindex 处理最容易被忽略
仅靠 CSS 隐藏(opacity: 0、visibility: hidden、position: absolute; left: -9999px)不会让元素退出 Tab 流。真正要跳过,得靠逻辑控制。
-
display: none或visibility: hidden的元素,即使有tabindex="0",也不会出现在 Tab 流中 -
disabled的<button disabled>或<input disabled>自动脱离 Tab 流,无需额外设tabindex="-1" - 用
aria-hidden="true"隐藏区域时,必须显式设tabindex="-1",否则仍可能被 Tab 到 - 现代方案优先用
inert属性(Chrome 100+、Firefox 117+、Safari 18.1+ 支持良好),它同时控制可见性、可访问性和焦点流
真正难的不是怎么写 tabindex,而是判断哪些元素该进 Tab 流、哪些该由 JS 主动聚焦、哪些该彻底隔离——可访问性不是加属性,是做决策;而 DOM 顺序本身,就是最可靠、最易维护的“顺序”。



















