原生 默认参与 tab 导航,不应加 tabindex;仅在模态框按钮预渲染禁用、aria-disabled 替代 disabled、或动态插入需程序聚焦时才设 tabindex="-1";模拟按钮的 / 才需 tabindex="0"+role="button"+键盘事件监听。

原生 <button></button> 默认就参与 Tab 导航,**不需要也不应该加 tabindex**——加了反而可能破坏焦点行为或触发框架警告。
为什么给 <button></button> 加 tabindex="0" 是错的
它本就在 Tab 流里,加 tabindex="0" 属于冗余声明,实际影响包括:
- React/Vue 中可能干扰 hydration 或焦点管理逻辑,导致 SSR 渲染后焦点位置不一致
- 某些旧浏览器(如 IE11)会把重复声明当作异常,悄悄重置 focus 状态
- 屏幕阅读器可能重复播报“按钮”,尤其当同时存在
role="button"时语义冲突 - 若误加
tabindex="-1",直接踢出 Tab 流——用户按 Tab 就跳过这个按钮,交互链断裂
<button></button> 什么时候才需要动 tabindex
极少数真实场景下,你才需干预它的 tabindex,且只用 -1:
- 模态框内某个确认按钮,在弹窗打开前已渲染但不可用,你想让它“存在但不可 Tab 到”,等弹窗激活后再用 JS 聚焦:此时设
tabindex="-1",再调button.focus() - 表单中某按钮被临时禁用(
disabled状态),但你还想保留程序化聚焦能力(比如恢复启用后立刻聚焦):不能用disabled(它会彻底阻断.focus()),改用aria-disabled="true"+tabindex="-1" - 动态插入的按钮(如异步加载的工具栏),DOM 挂载后需立即聚焦:确保它已有
tabindex="-1",否则.focus()静默失败
真正要加 tabindex 的不是按钮,而是它的“替身”
当你用 <div> 或 <code><span></span> 模拟按钮行为时,才轮到 tabindex 出场:
- 必须加
tabindex="0",否则键盘用户根本 Tab 不到它 - 必须同步加
role="button",否则屏幕阅读器读不出可操作语义 - 必须监听
keydown,对Enter和空格键(' ')做响应;空格要e.preventDefault(),否则触发页面滚动 - 若 CSS 移除了默认
outline,得补:focus-visible { outline: 2px solid #007bff; },否则焦点不可见
复杂点在于:焦点顺序只认 DOM 位置,不认视觉布局。Grid 或 Flex 排版后,tabindex="0" 的元素仍按 HTML 源码顺序聚焦——哪怕它被 CSS 拉到了右下角。别指望靠 tabindex="1" 把它“提前”,那只会让整个 Tab 流失控。











