tabindex="0"仅用于需键盘聚焦的非原生可聚焦元素(如),须配role、keydown监听和:focus-visible样式;tabindex="-1"专用于js主动聚焦,正整数tabindex会破坏焦点流且被wcag明确反对。

tabindex 不是用来给元素“排号”的,设成 1、2 或 99 都是错的;真正该用的只有 0 和 -1,其余数值会破坏焦点流、误导屏幕阅读器、且在动态内容中极易失效。
什么时候必须加 tabindex="0",什么时候不该加
只对有明确交互意图的非原生可聚焦元素加 tabindex="0",比如 <div role="button">、<code><h3 role="tab"></h3>、自定义折叠面板标题。原生 <button></button>、<a href></a>、<input> 本来就进 Tab 流,加了反而可能干扰 React 焦点管理或 SSR hydration。
常见错误包括:
- 给纯展示文本容器(如
<div class="avatar"><img></div>)加tabindex="0",键盘用户多停一次却无事可做 - 给已包裹原生可聚焦子元素的容器(如内部有
<button></button>的卡片)再加tabindex="0",导致双重焦点停靠 - 在 CSS Grid 或 Flex 布局中盲目加
tabindex="0",却没检查实际 DOM 顺序——浏览器只认源码位置,不认视觉重排
tabindex="-1" 不是隐藏开关,是程序聚焦的入口
tabindex="-1" 的唯一合法用途:让元素**不能被 Tab 键碰到,但能被 .focus() 主动聚焦**。它不是“禁用”,也不是“隐藏”,而是为 JS 控制留出通道。
典型场景和注意事项:
- 模态框打开后,第一个可操作元素(如确认按钮)必须提前带
tabindex="-1",否则element.focus()调用静默失败 - 不要给整个弹窗容器设
tabindex="-1",而应设在首个可操作子元素上(如首项<input>或<button></button>) - 异步加载的内容(如懒加载表单字段)需确保 DOM 已挂载后再调
.focus(),可用requestAnimationFrame或setTimeout(() => el.focus(), 0) - 聚焦后若目标被
overflow: hidden或transform遮挡,必须手动补element.scrollIntoView({ block: 'nearest' })
为什么永远不要用 tabindex="1" 及更高正数
写 tabindex="1" 不会让它变成“第一个被 Tab 到的元素”,只会把它塞进所有正数里按数值升序排——但多个 tabindex="1" 仍按 DOM 顺序走,和没设一样。更糟的是,一旦混入 tabindex="0" 的链接或按钮,Tab 流变成“正数 → 0 值 → 其他”,完全失控。
真实影响包括:
- 移动端 Safari 直接无视大部分
tabindex="0",tabindex="1"更无效,还可能触发警告 - React/Vue 动态插入新组件后,硬编码的正数无法重算,焦点跳转错乱
- 屏幕阅读器把第一个
tabindex="1"当作逻辑起点,如果它是页脚的“导出按钮”,用户一进页面就听到“导出”,主编辑区上下文全丢 -
focus({ preventScroll: true })在 Safari 中不支持,传对象参数会静默失败;建议先检测'preventScroll' in FocusOptions.prototype
配套三件套缺一不可:role + keydown + focus 样式
加了 tabindex="0" 只解决“能被 Tab 到”,不解决“按了能干啥”。必须同步完成以下三件事:
- 加
role属性,例如role="button"或role="checkbox",否则屏幕阅读器读作“div”或“无名容器” - 监听
keydown,对Enter和Space做响应;Space必须e.preventDefault(),否则页面滚动 - 提供可见焦点样式,推荐用
:focus-visible,避免鼠标用户看到突兀轮廓;若移除了outline,务必补上outline: 2px solid #007bff类样式
最常被忽略的点是:焦点样式在高对比度模式下是否仍可见,以及 focus() 后元素是否真的出现在可视区域内——这两处一漏,键盘用户就彻底迷失。











