给input写tabindex="1"会让焦点乱跳,因为浏览器将所有正整数tabindex元素统一提前到tab流最前面并按数值升序排列,导致其脱离dom顺序,与视觉/逻辑顺序错位,且动态渲染时极易失效。

默认情况下,表单元素的 Tab 焦点顺序就是 HTML 源码顺序,不需要任何 tabindex 设置——加了反而容易出错。
为什么给 input 写 tabindex="1" 会让焦点乱跳
浏览器把所有 tabindex 为正整数的元素(如 tabindex="1"、tabindex="5")统一提前到整个页面 Tab 流最前面,再按数值升序排列。这意味着:
- 你设了
tabindex="1"的<input>可能跳到页脚的按钮后面,只因那个按钮写了tabindex="0"(它按 DOM 顺序排在最后) - 多个
tabindex="1"元素中,只有第一个按 DOM 位置生效,其余被忽略 - React/Vue 动态插入新字段时,若它们也带正数
tabindex,旧顺序立刻失效
哪些情况真需要写 tabindex
绝大多数表单不用动 tabindex,但以下三类场景必须干预:
- 非原生可聚焦元素参与 Tab 流:比如用
<div role="button"> 实现的开关,必须加 <code>tabindex="0"+role="button"+keydown监听 - 临时跳过某个字段但保留 JS 聚焦能力:用
tabindex="-1"(不是-999或空值),之后仍可调用element.focus() - 模态框打开后强制聚焦首个输入框:模态容器本身设
tabindex="-1",再对目标<input>调用.focus() -
document.querySelectorAll('input')会拉进type="hidden"、disabled、hidden的元素,得过滤:input:not([disabled]):not([hidden])[type="text"], input[type="email"], input[type="password"] - 元素可能被
display: none或visibility: hidden隐藏,需额外检查offsetParent !== null - DOM 动态增删字段后,缓存的元素列表就失效了,每次都要重新
querySelectorAll - 在
textarea中按回车应换行,不是切焦点——必须判断当前元素类型,或统一用e.key === 'Enter'+e.preventDefault()控制逻辑分支 <tr tabindex="0"> 在首次加载后无法被 Tab 到,<code>.focus()也会静默失败- 解决办法是初始化时对第一行主动调一次
.focus({preventScroll: false})(注意不能传preventScroll: true,Safari 不支持) - 用
<div tabindex="0"> 模拟可聚焦区域时,必须同步加 <code>role="region"或对应语义role,否则屏幕阅读器不识别真正可控的焦点流,靠的是 HTML 结构顺序 + 极简
tabindex使用 + 运行时动态筛选可用元素。那些写死的tabindex="999"或全表单统一设tabindex="1"的做法,在 DOM 变动、多端兼容、辅助技术接入时,第一个崩的就是它。
回车键切换焦点比 Tab 更难搞准
单纯监听 keyup 并调 focus() 很容易失败,因为:
移动端 Safari 的 tabindex 是个“半残”功能
iOS Safari 对未被用户主动触碰过的页面执行严格限制:











