tabindex="0" 是让非按钮元素可被tab键聚焦的前提,但需配合role="button"提供语义,且应置于dom靠前位置以优化焦点顺序,同时须处理焦点可见性、滚动可视及enter/space触发逻辑。

tabindex="0" 是让按钮可被 Tab 键定位的前提
默认情况下,<button></button> 本身就能被 Tab 键聚焦,无需额外设置。但如果你用的是 <div>、<code><span></span> 或 <a></a>(无 href)模拟按钮,它就不会进 Tab 流——这时必须加 tabindex="0"。
注意:加了 tabindex="0" 只是让它“可聚焦”,不等于它“是按钮”。屏幕阅读器仍会读作 “div”,不是“按钮”。所以还得补上 role="button",否则键盘用户无法理解其用途。
- ✅ 正确:
<div tabindex="0" role="button">提交</div> - ❌ 错误:
<div tabindex="0">提交</div>(语义缺失) - ⚠️ 危险:
<div tabindex="1">提交</div>(打乱自然顺序,后续元素焦点路径不可控)
Tab 键不会“跳到指定按钮”,只按 DOM 顺序遍历
浏览器不支持“按一次 Tab 就跳到 id=save 的按钮”这种跳转。Tab 键的行为是线性的:从当前焦点元素出发,按 HTML 源码中出现的先后顺序,找下一个可聚焦元素。
所以想让某个按钮更快被 Tab 到,唯一可靠方式是把它在 DOM 中提前——比如把关键操作按钮(如“保存”“提交”)放在表单顶部或靠近触发入口的位置,而不是塞在页脚或折叠面板里。
- 若按钮在
<dialog></dialog>或模态框内,打开时需手动调用element.focus(),且该按钮应提前设tabindex="-1"(仅用于编程聚焦,不参与 Tab 流) - 若按钮被
display: none或hidden隐藏,Tab 键自动跳过;但用visibility: hidden或opacity: 0仍会被聚焦(只是看不见) - 父级有
inert属性,或自身有disabled、aria-disabled="true",也会被跳过
如何让 Tab 键在有限几个按钮间循环(比如登录表单)
原生 Tab 行为是全局遍历,但你可以用 JavaScript 截获 keydown 事件,在特定区域(如 <form></form>)内限制焦点范围。
关键点在于:监听 Tab 键时,阻止默认行为(e.preventDefault()),然后手动计算下一个目标并 .focus()。
document.getElementById('loginForm').addEventListener('keydown', e => {
if (e.key !== 'Tab') return;
const focusable = Array.from(
e.currentTarget.querySelectorAll('button, input, [tabindex="0"]')
).filter(el => el.offsetParent !== null);
const currentIndex = focusable.indexOf(e.target);
let nextIndex = e.shiftKey ? (currentIndex - 1 + focusable.length) % focusable.length
: (currentIndex + 1) % focusable.length;
focusable[nextIndex].focus();
e.preventDefault();
});
- 必须过滤掉
offsetParent === null的元素(隐藏/不可见) - 要同时处理
Shift + Tab(反向循环),否则键盘用户会卡死 - 别用 jQuery 的
:visible,它对opacity: 0或transform: scale(0)判断不准
视觉反馈缺失会让键盘用户完全迷失
很多团队加了 tabindex 却没配 :focus 样式,结果 Tab 键确实聚焦了按钮,但用户看不到焦点在哪——尤其当全局 CSS 里写了 outline: none 却没提供替代样式时。
推荐写法:
:focus-visible {
outline: 2px solid #007bff;
outline-offset: 2px;
}
- 优先用
:focus-visible而非:focus,避免鼠标点击时也出轮廓 - 不要只靠颜色变化,至少叠加边框或背景色,确保满足 WCAG 对比度要求
- 滚动容器内按钮被 Tab 到却看不见?要配合
element.scrollIntoView({ block: 'nearest' }),.focus()不会自动滚动
最常被忽略的是:按钮获得焦点后,用户习惯按 Enter 或 Space 触发,所以必须监听这两个键并调用 .click(),否则空格键会滚动页面。











