tabindex合法值仅0和-1;必须加tabindex="0"的是需键盘访问的非原生元素(如<div role="button">),原生控件(<button>、<input>等)绝对不可加,正整数tabindex会破坏焦点流且被WCAG明确反对。

tabindex 的合法值只有 0 和 -1,其他任何正整数(如 tabindex="1")都会破坏焦点流、误导屏幕阅读器、在动态渲染中失效——这不是建议,而是 WCAG 明确反对的行为。
哪些元素必须加 tabindex="0",哪些绝对不能加
只对默认不可聚焦、但业务上需键盘进入的容器加 tabindex="0",比如 <div role="button">、<h3 role="tab">、自定义折叠面板标题栏。这些元素不带它,键盘用户 Tab 到那里就直接跳过。
原生可聚焦元素(<button>、<a href>、<input>、<select>、<textarea>)**绝不能加** tabindex="0"——它们天然进 Tab 流,加了反而可能干扰 React hydration 或 SSR 渲染逻辑。
- 给纯展示文本或图标容器(如
<div class="avatar"><img></div>)加tabindex="0",会导致键盘多停一次却无事可做,还污染屏幕阅读器播报 - 给已包裹
<button>的卡片外层再加tabindex="0",会造成双重焦点停靠,用户按一次 Tab 却触发两次停顿 - Grid/Flex 布局中盲目加
tabindex="0",却不检查实际 DOM 顺序——浏览器只认源码位置,不认 CSSorder或grid-area
tabindex="-1" 不是隐藏开关,是编程聚焦的入口
tabindex="-1" 的唯一合法用途:让一个具体可交互元素无法被 Tab 键碰到,但允许 JS 主动调用 .focus() 聚焦它。它不是“禁用”,也不是“隐藏”。
立即学习“前端免费学习笔记(深入)”;
典型失败场景:模态框打开后 modal.querySelector('button').focus() 静默失败——大概率是那个按钮是 <div>,没提前设 tabindex="-1";而原生 <button> 根本不需要它,加了反而主动踢出 Tab 流。
- 不要给整个弹窗容器或
role="tabpanel"设tabindex="-1",应设在首个可操作子项(如确认按钮、首项<input>)上 - 异步加载的内容(如懒加载表单字段)必须等 DOM 挂载完成再调
.focus(),可用requestAnimationFrame(() => el.focus())或setTimeout(() => el.focus(), 0) - 聚焦后若目标被
overflow: hidden、transform或position: absolute遮挡,必须手动补el.scrollIntoView({ block: 'nearest' })
为什么永远不要写 tabindex="1" 及更高正数
写 tabindex="1" 不会让它变成“第一个被 Tab 到的元素”,只会把它塞进所有正数里按数值升序排——而所有正数元素都会排在原生可聚焦元素之后。结果往往是页脚的“导出”按钮比主编辑区还先获得焦点。
真实影响远不止顺序错乱:
- 移动端 Safari 直接无视大部分正数
tabindex,还可能触发控制台警告 - React/Vue 动态插入新组件后,硬编码的
tabindex="99"无法重算,焦点跳转错乱 - 多个
tabindex="1"元素中,浏览器只认 DOM 中第一个,其余无效 - 屏幕阅读器把第一个
tabindex="1"当作逻辑起点,哪怕它是页脚按钮,主上下文全丢
真正要控制顺序,靠的是 HTML 源码位置——导航栏放顶部,它就第一个被 Tab 到;表单字段按语义顺序书写,焦点自然线性推进。
加了 tabindex="0" 后,你还没做完的事
tabindex="0" 只解决“能被 Tab 到”,不解决“按了回车或空格能不能响应”。这一步漏掉,就违反 WCAG 2.1 键盘可访问原则。
- 必须同步加
role属性,例如role="button",否则屏幕阅读器读作“普通容器”,用户不知道这是可操作项 - 必须监听
keydown,且只对event.key === 'Enter'或event.key === ' '(注意是空格字符)做响应;Space 触发时记得e.preventDefault(),否则页面会滚动 - CSS 若移除了
outline,必须补:focus-visible { outline: 2px solid #007bff; },否则键盘用户完全看不见当前焦点在哪 - iOS Safari 默认只聚焦表单控件和链接;即使设了
tabindex="0",<div>在触摸模式下也不会被 Tab 导航到——除非用户手动开启“辅助功能 > 键盘 > 全键盘访问”
最常被忽略的一点:tabindex 不是独立存在的属性,它必须和 role、keyboard event、focus style、DOM 顺序四者协同生效,缺一即残。



















