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

原生语义化标签能直接规避大多数键盘焦点陷阱,不用额外写 JS 就能保证 Tab 流自然、可进可出。
为什么用 <button></button> 比用 <div tabindex="0"> 更安全
<p>因为 <code><button></button> 天生支持 Enter/Space 触发、默认可聚焦、禁用状态(disabled)自动移出 Tab 流,且屏幕阅读器能准确播报“按钮”类型和状态。而 <div tabindex="0"> 只解决了“能被 Tab 到”,但没解决“按了有没有反应”——你得手动监听 <code>keydown、区分 Enter 和 Space 的触发时机、调用 preventDefault()、再模拟点击逻辑。
- 常见错误:只加
tabindex="0" 却没监听 keydown,键盘用户停在元素上却无法操作
- 更隐蔽的坑:给
<div> 加 <code>role="button" 后,屏幕阅读器会读作“按钮”,但空格键在 keyup 才生效,若只监听 keydown 就完全没响应
- 性能影响:每个自定义按钮都要重复写焦点管理、键盘事件、状态同步逻辑,维护成本指数级上升
tabindex="-1" 的唯一合法用途是程序化聚焦
它让元素能被 .focus() 主动获取焦点,但不进入 Tab 顺序——这是模态框首次打开时聚焦第一个输入框、选项卡切换后聚焦内容区首项的标准做法。滥用会导致焦点不可达或行为错乱。
- 别给
<input> 或 <button></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 又跳不出去。
tabindex="0" 却没监听 keydown,键盘用户停在元素上却无法操作<div> 加 <code>role="button" 后,屏幕阅读器会读作“按钮”,但空格键在 keyup 才生效,若只监听 keydown 就完全没响应
tabindex="-1" 的唯一合法用途是程序化聚焦
它让元素能被 .focus() 主动获取焦点,但不进入 Tab 顺序——这是模态框首次打开时聚焦第一个输入框、选项卡切换后聚焦内容区首项的标准做法。滥用会导致焦点不可达或行为错乱。
- 别给
<input>或<button></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 又跳不出去。











