tabindex="0"是使非原生元素可键盘聚焦的合理方式,需配合role、keydown监听及:focus-visible样式;tabindex="-1"用于编程聚焦,正整数tabindex被wcag不推荐,应依赖dom顺序控制焦点流。

tabindex="0" 是让非原生元素进 Tab 流的唯一合理方式
原生可聚焦元素(<button></button>、<a href></a>、<input>)默认就在 Tab 流里,加 tabindex="0" 不仅多余,还可能干扰 SSR 渲染或 React 的焦点管理。只有 <div>、<code><span></span> 这类语义容器需要键盘可访问时,才该加 tabindex="0"。
但加了不等于完事:
- 必须同步加
role属性,比如role="button",否则屏幕阅读器读不出操作意图 - 必须监听
keydown,对Enter和空格键(' ')都做响应,并调用event.preventDefault()防止空格滚动页面 - CSS 中若移除了
outline,必须提供:focus-visible或至少:focus样式,否则键盘用户完全看不到焦点在哪
tabindex="-1" 不是隐藏开关,而是编程聚焦的入口
tabindex="-1" 的作用很明确:元素不会出现在 Tab 键顺序中,但能被 element.focus() 主动聚焦。这是模态框、折叠面板、动态菜单等场景下接管焦点的唯一可靠方式。
常见失效原因:
- 给
<button></button>或<a></a>加tabindex="-1"—— 它们本就在 Tab 流里,加了等于主动踢出 - 模态框打开后调用
.focus()失败,大概率是因为目标元素没设tabindex="-1"(尤其当它是<div> 时) <li>元素被 <code>display: none或visibility: hidden隐藏后,.focus()会静默失败,哪怕有tabindex="-1" - 在 Safari 中传
{ preventScroll: true }参数会报错,需先检测支持性 - 页面其他区域也有
tabindex="1",焦点跳转不可控 - 多个相同值(如全设为
tabindex="1")时,浏览器仍按 DOM 插入顺序走,不是你写的顺序 - React/Vue 动态渲染后,服务端生成的顺序和客户端 hydration 后的 tabindex 值极易不一致
- 移动端 Safari 对正整数支持不稳定,部分版本直接忽略
- 用
flex-direction: column-reverse或order改变视觉顺序时,DOM 顺序不变 → 键盘导航和读屏仍按原始结构走 - 动态插入的按钮(如 JS 渲染的分页控件),插入位置必须符合语义流,不能只考虑视觉布局
-
aria-hidden="true"不影响tabindex行为 —— 若同时存在,必须显式设tabindex="-1",否则键盘用户能聚焦却听不到描述
正整数 tabindex(如 "1"、"5")必须禁用
浏览器把所有 tabindex="n"(n > 0)的元素统一排在 tabindex="0" 元素之前,并按数值升序排列。你以为设了 tabindex="1" 就能第一个聚焦,实际可能:
更关键的是:WCAG 明确不推荐,90% 的键盘用户会在这种“插队”逻辑中迷失上下文。
真正可控的聚焦顺序靠 DOM 位置 + tabindex="0"
键盘导航和屏幕阅读器都依赖 DOM 顺序理解结构。想让筛选按钮在筛选结果上方、提交按钮在表单底部,不是靠 tabindex="1" “拖拽”,而是靠 HTML 本身的物理位置。
容易被忽略的细节:
复杂交互(如下拉建议列表)应优先用 aria-activedescendant + 容器 tabindex="0" + 选项 tabindex="-1",而不是给每个选项都塞进 Tab 流。











