tabindex="0"是让非原生元素进入tab流的唯一合理方式,需同步加role、监听keydown处理enter/空格、提供:focus-visible样式;tabindex="-1"用于程序化聚焦;禁用正整数tabindex,原生控件无需设tabindex。

tabindex="0" 是让非原生元素进 Tab 流的唯一合理方式
它不改变 DOM 顺序,只声明“这个元素愿意按结构自然加入键盘导航流”。比如一个 div 带 onclick,不加 tabindex="0",键盘用户就完全 Tab 不到它。
但加了只是“能被 Tab 到”,不是“能用”:
- 必须同步加
role,例如role="button",否则屏幕阅读器读不出可操作语义 - 必须监听
keydown,手动处理Enter和空格键(' '),并调用event.preventDefault()防止空格滚动页面 - 必须提供
:focus-visible样式,否则键盘用户看不到焦点在哪 - 别给纯展示性元素(如图标容器、装饰
span)加,会增加无效停靠点
tabindex="-1" 是程序化聚焦的唯一入口
它让元素彻底退出 Tab 键遍历路径,但保留被 .focus() 主动聚焦的能力。这是模态框、下拉菜单、折叠面板等动态组件控制焦点流转的核心机制。
常见误用是只写 .focus() 却没提前设 tabindex="-1",结果静默失败:
- 模态框内第一个按钮或输入框,HTML 中就得带
tabindex="-1",打开后立刻element.focus() - Accordion 展开后,焦点要落到新暴露的首项,而不是留在触发标题上
- 关闭弹层前,需记录触发源(如
document.activeElement),关完立即回焦 -
display: none或visibility: hidden的元素即使有tabindex="-1",.focus()也会失败
为什么正整数 tabindex(如 "1", "2")必须禁用
浏览器对正整数的处理逻辑是:所有 tabindex > 0 的元素会被提到整个 Tab 流最前面,再按数值升序排列;同值时仍退回到 DOM 顺序。这不是“精细排序”,而是破坏性重排。
实际后果很直接:
- 多个
tabindex="1"元素,只有第一个真正生效,其余被降级为0 - 原生可聚焦元素(
button、input)默认无tabindex,会被挤到最后,导致 Tab 从页脚按钮开始 - React/Vue 动态插入节点后,正数
tabindex容易错位,SSR 还可能触发 hydration 警告 - 第三方组件库若也用正数,Tab 流立刻失控,两个
tabindex="1"都变成“第一个”
原生控件要不要设 tabindex
不要。按钮、输入框、带 href 的链接这些原生可聚焦元素,默认就等效于 tabindex="0"。给它们加 tabindex="0" 是冗余,加 tabindex="-1" 是主动踢出 Tab 流——比如一个 button 被设了 tabindex="-1",键盘用户就再也 Tab 不到了。
真正要加 tabindex 的,是那些用 div 或 span 模拟的控件:
- 必须加
tabindex="0",否则无法进入 Tab 流 - 必须同步加
role属性,否则屏幕阅读器读不出功能 - 必须监听
keydown,对Enter和空格键做相同响应,并调用preventDefault() - 必须提供
:focus-visible样式,否则键盘用户看不到焦点在哪
DOM 结构决定 Tab 顺序,不是 CSS 布局,也不是 tabindex 数值。最容易被忽略的是:加了 tabindex="0" 后不补 role 和 keydown,等于只开了门却没装锁和把手——键盘用户能进来,但不知道怎么用,也看不见自己在哪。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











