tabindex="0"是使非原生可聚焦元素进入Tab流的必要操作,需同步添加role、处理键盘事件并提供焦点样式;tabindex="-1"用于程序化聚焦,正整数tabindex应禁用;优先使用原生可聚焦元素。

tabindex="0" 是让非原生可聚焦元素进 Tab 流的唯一合理选择
默认不可聚焦的元素(比如 div、span、article)加了 tabindex="0",才会按 DOM 顺序自然进入 Tab 键循环。这不是“锦上添花”,而是补足语义缺口的底线操作。
但光加这个远远不够——它只解决“能被 Tab 到”,不解决“按了能干啥”:
- 必须同步加
role属性,例如div role="button",否则屏幕阅读器读不出行为意图 - 必须监听
keydown,手动处理Enter和Space键,event.preventDefault()后触发对应逻辑 - 必须提供可见的焦点样式,推荐用
:focus-visible,避免鼠标用户看到突兀轮廓 - 别给纯展示性内容(如图标
span、装饰性div)加tabindex="0",这只会增加无效停靠点
tabindex="-1" 是程序化聚焦的唯一安全通道
tabindex="-1" 的作用非常明确:禁止用户通过 Tab 键到达,但允许 JS 调用 .focus() 主动聚焦。模态框、展开面板、下拉菜单首次打开时的焦点控制,全靠它。
常见错误是只写 .focus() 却没提前设 tabindex="-1",结果调用失败或静默忽略:
- 模态框内第一个可操作元素(如确认按钮)需在 HTML 中就带
tabindex="-1",打开后立刻element.focus() - 关闭模态框前,应记录触发它的元素(如
document.activeElement或传参),关完立即triggerEl.focus() - 用
aria-hidden="true"隐藏的区域,其内部元素仍可能被 Tab 到,必须显式加tabindex="-1"或现代inert属性 - 仅靠
opacity: 0或position: absolute; left: -9999px隐藏的元素,Tab 键仍会停靠,必须配合tabindex="-1"
正整数 tabindex(如 1、2、5)必须彻底禁用
浏览器对正整数 tabindex 的排序逻辑不可靠:多个正数时虽大致升序,但漏值(比如只有 1 和 3)不会自动补位;原生可聚焦元素(button、a、input)会被挤到所有正数之后,导致 Tab 流断层。
更严重的是,它在动态场景中必然失控:
- 组件复用或 SSR + hydration 时,服务端与客户端生成的正数值极易不一致,引发焦点错位警告
- 屏幕阅读器依赖文档流理解结构,人为打乱顺序等于剥夺上下文感知能力
- 视觉权重高的区域(如 banner)不该靠
tabindex="1"提前,而应靠源码位置——导航栏在顶部,就让它第一个被 Tab 到
原生元素永远优于自定义 + tabindex 组合
想做一个按钮?直接用 button。它自带聚焦、Enter/Space 触发、disabled 语义、role="button" 原生支持,CSS 重置样式即可。
用 div + onclick 模拟按钮,等于自己实现一整套无障碍链路,且极易遗漏:
- 漏加
tabindex="0"→ 键盘用户根本点不到 - 漏监听
Space键 → 键盘用户按空格无反应(Enter只是部分兼容) - 漏设
aria-pressed(切换类)或aria-disabled="true"→ 状态对屏幕阅读器不可见 - 只靠
opacity: 0.5和移除tabindex表示禁用 → 焦点仍可能抵达,只是看起来不能点
真正难的不是写 tabindex,而是每处交互都闭环考虑:键盘路径是否连通、语义角色是否准确、事件响应是否完整。一个没加 tabindex="0" 的卡片标题,或一处没处理 Space 的自定义开关,就足以让依赖键盘的用户卡死。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











