原生语义化标签(如<button>)能天然规避键盘焦点陷阱,无需JS即可保障Tab流自然、可进可出;因其默认支持Enter/Space触发、可聚焦、disabled自动移出Tab流,且屏幕阅读器准确播报类型与状态,而<div tabindex="0">仅解决可聚焦,需手动处理键盘事件、状态同步等,易出错且维护成本高。

原生语义化标签能直接规避大多数键盘焦点陷阱,不用额外写 JS 就能保证 Tab 流自然、可进可出。
为什么用 <button> 比用 <div tabindex="0"> 更安全
因为 <button> 天生支持 Enter/Space 触发、默认可聚焦、禁用状态(disabled)自动移出 Tab 流,且屏幕阅读器能准确播报“按钮”类型和状态。而 <div tabindex="0"> 只解决了“能被 Tab 到”,但没解决“按了有没有反应”——你得手动监听 keydown、区分 Enter 和 Space 的触发时机、调用 preventDefault()、再模拟点击逻辑。
- 常见错误:只加
tabindex="0"却没监听keydown,键盘用户停在元素上却无法操作 - 更隐蔽的坑:给
<div>加role="button"后,屏幕阅读器会读作“按钮”,但空格键在keyup才生效,若只监听keydown就完全没响应 - 性能影响:每个自定义按钮都要重复写焦点管理、键盘事件、状态同步逻辑,维护成本指数级上升
tabindex="-1" 的唯一合法用途是程序化聚焦
它让元素能被 .focus() 主动获取焦点,但不进入 Tab 顺序——这是模态框首次打开时聚焦第一个输入框、选项卡切换后聚焦内容区首项的标准做法。滥用会导致焦点不可达或行为错乱。
- 别给
<input>或<button>加tabindex="-1":它们本就可聚焦,加了反而踢出 Tab 流 - 别给整个模态框容器设
tabindex="-1":.focus()落在空容器上,Tab 键仍会跳出 - 移动端 Safari 不支持
focus({preventScroll: true}),若没做兼容检测,调用会静默失败,焦点看似设置了,实际用户看不见
模态框焦点锁死必须同时满足三个条件
只做其中一两个,键盘用户依然会被困住。浏览器不会自动限制 Tab 范围,全靠代码兜底。
立即学习“前端免费学习笔记(深入)”;
- 打开时:用
requestAnimationFrame延迟聚焦首个可聚焦子元素(如modal.querySelector('button, input, [tabindex="0"]')),避免 DOM 还没渲染完就调用.focus() - 运行中:监听模态框容器的
keydown,仅当按下Tab且当前焦点在最后一个/第一个可聚焦项时,才event.preventDefault()并手动切到对应端点 - 关闭后:必须提前缓存触发源(如
openBtn.id),关闭时校验该元素是否仍在 DOM 中,再.focus()——否则焦点可能落在body或消失
真正难的不是写对某一行代码,而是所有环节必须协同:语义标签决定基础能力,tabindex 控制访问入口,JS 焦点管理封住边界,ARIA 属性告诉辅助技术“现在是什么状态”。漏掉任意一环,键盘用户就可能卡在某个看不见的 div 上,按空格没反应,按 Tab 又跳不出去。



















