tabindex="0"只应加在非原生可聚焦但需键盘操作的元素上,如、等,必须同步配role、keydown监听和:focus-visible样式;原生控件无需设置。

tabindex="0"加在哪?先看元素是否原生可聚焦
原生可聚焦元素(button、a[href]、input、select、textarea)**不要加** tabindex="0"——它们默认就进 Tab 流,加了反而可能干扰 SSR hydration 或 React 焦点管理逻辑。
真正需要加 tabindex="0" 的,是那些“本不该能聚焦但业务又必须支持键盘操作”的容器,比如:
<div role="button">点击展开</div><h3 role="tab">面板标题</h3>-
<article role="region" aria-labelledby="section1"></article>(仅当它需作为整体被键盘用户跳转到时)
常见错误是给纯图标 span、装饰性 div 或静态文本加 tabindex="0",结果键盘多停一次却无事可做,还污染屏幕阅读器播报流。
tabindex="-1"该聚焦谁?不是容器,而是第一个可操作子项
模态框打开后调用 .focus() 失败?大概率是你把 tabindex="-1" 加在了模态框根容器上,而不是第一个可操作子元素。
tabindex="-1" 的唯一合法用途是:让一个**具体可交互元素**不被 Tab 键碰到,但允许 JS 主动聚焦。它不是“隐藏开关”,更不是“禁用”。
正确做法:
- 模态框内第一个按钮(如“确认”)在 HTML 中就带
tabindex="-1" - JS 打开模态框后,立刻执行
confirmBtn.focus() - 关闭前记录触发源(如
document.activeElement),关完立即回焦 - 别给整个
role="tabpanel"容器设tabindex="-1"——这会阻断内部所有input、button的自然聚焦能力
注意:目标元素若尚未渲染(如异步加载的表单字段),或被 display: none / visibility: hidden 隐藏,.focus() 会静默失败。
为什么永远不能写 tabindex="1"
写 tabindex="1" 不会让它变成“第一个被 Tab 到的元素”,只会让它和所有其他正数一起,被浏览器塞到 Tab 流最前面,然后按数值升序排列——而原生可聚焦元素(button、a)会被挤到最后。
真实后果包括:
- 多个
tabindex="1"元素中,浏览器只认第一个,其余无效 - React/Vue 动态插入新组件后,硬编码的正数无法重算,焦点跳转错乱
- 移动端 Safari 直接无视大部分正数
tabindex,还可能触发控制台警告 - 屏幕阅读器把第一个
tabindex="1"当作逻辑起点,哪怕它是页脚的“导出”按钮
真正要控制顺序,靠的是 HTML 源码位置——导航栏放顶部,它就第一个被 Tab 到;不需要靠数字插队。
:focus-visible 样式漏掉,等于没做无障碍
清掉默认 outline 后没补 :focus-visible,键盘用户根本看不见焦点在哪——这是最常见也最致命的遗漏。
:focus-visible 只在键盘触发聚焦时生效,鼠标点击/触摸后自动退去,兼顾体验与可访问性:
- 别用
:focus替代——它对鼠标用户也显示轮廓,体验变差 - 别靠 JS 检测
KeyboardEvent手动切样式——兼容性和维护成本高 - 若需兼容旧浏览器,可用
data-focus-visible-added类配合 polyfill,但现代项目应优先用原生:focus-visible
复杂点在于:网格布局、动态加载内容、模态框嵌套等场景下,焦点路径依赖 DOM 顺序 + role + aria 关联,tabindex 只管“能不能停”,不管“停了之后怎么走”。真正难的不是选对值,而是理解它只是线性路径上的一个锚点,不是方向控制器。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











