tabindex="0"应仅加在语义缺失但需键盘导航的容器上,如、等;原生可聚焦元素无需添加,装饰性元素添加属反模式。

tabindex="0"该加在哪些元素上才不翻车
原生可聚焦元素(<button></button>、<a href></a>、<input>、<select></select>、<textarea></textarea>)默认就在 Tab 流中,加 tabindex="0" 不但多余,还可能在 SSR/hydration 场景下导致焦点错位或重复聚焦。
真正需要加的,是那些语义缺失但业务上必须支持键盘导航的容器,比如:
<div role="button"> —— 必须配 <code>tabindex="0"+role="button"+keydown监听-
<h3 role="tab"></h3>—— 配tabindex="0"后才能被 Tab 进入,再用方向键切换 - 自定义折叠面板标题栏、卡片式操作区等非表单容器
- 给整个下拉菜单根
<div> 设 <code>tabindex="-1",结果整个区域对键盘不可见 —— 正确做法是容器设tabindex="0",选项设tabindex="-1" - 给
<button></button>加tabindex="-1",等于主动踢出 Tab 流,用户按 Tab 就跳过它 - 动态插入后立刻
.focus(),但元素尚未完成渲染或样式计算(尤其 React/Vue 中),导致静默失败 - 多个
tabindex="1"元素中,浏览器只认 DOM 中第一个,其余被忽略 - 混用
tabindex="1"和tabindex="2"后,Tab 顺序变成「所有正数升序 → 原生元素按 DOM 顺序 → 所有tabindex="0"元素」,彻底脱离用户预期 - 在 SSR 或 hydration 场景下,这个值根本不可维护,容易因服务端/客户端渲染差异导致焦点错位
-
focus({ preventScroll: true })在 Safari 中不支持,传对象参数会静默失效,必须先检测:if ('preventScroll' in FocusOptions.prototype) - 用
opacity: 0或position: absolute; left: -9999px隐藏的元素,仍会被 Tab 到 —— 必须配合inert属性或显式移除tabindex
纯装饰性元素(如图标 <svg></svg> 容器、空 <span></span>)加 tabindex="0" 是反模式,会制造无意义停顿点,直接删掉。
为什么 tabindex="-1" 不能乱用在容器上
tabindex="-1" 的唯一合法用途是:让 JS 能调用 .focus(),但用户无法通过 Tab 键到达它。它不是“隐藏开关”,也不是“禁用焦点”的替代方案。
常见错误包括:
安全做法:确保目标元素已挂载、非 display: none、未被 disabled,再调用 .focus();必要时用 requestAnimationFrame 延迟一帧。
正整数 tabindex(如 "1")为什么必须立刻删除
tabindex="1" 看似“想让它第一个被 Tab 到”,实际行为完全相反:所有正数元素会被浏览器统一归到原生可聚焦元素之后,再按数值升序排列 —— 导致焦点路径断裂、屏幕阅读器逻辑错乱。
更糟的是:
如果你真需要控制焦点进入时机(比如“跳过此步”链接),优先用 tabindex="-1" + JS 在合适时机(如上一字段失焦后)显式 .focus(),而不是靠正数强行插队。
移动端 Safari 和 focus() 的兼容陷阱
iOS Safari 对 tabindex 支持有限:默认只聚焦表单控件和链接;即使设了 tabindex="0",<div> 在触摸模式下也不会被 Tab 导航到 —— 除非用户先触发一次手势(如点击、长按)。
<p>这意味着:</p>
<ul>
<li>模态框打开后调 <code>.focus() 可能静默失败,需配合用户手势上下文(例如在按钮 click 回调中立即聚焦)
最易被忽略的一点:CSS 的 outline: none 会直接干掉键盘用户的焦点指示,务必用 :focus-visible 替代,并确保它在所有交互状态(如 hover + focus)下都生效。











