弹出层焦点锁定需事件监听、主动聚焦与DOM范围判断,仅设tabindex="-1"无效;首个可操作元素须加tabindex="-1"并手动管理Tab循环及关闭后焦点回退。

弹出层(Modal)的焦点锁定不能靠 tabindex 单独实现,必须配合事件监听、主动聚焦和 DOM 范围判断——否则键盘用户一定会跳出 Modal,导致可访问性失败。
为什么给 Modal 根元素设 tabindex="-1" 没用
很多人以为在 <div class="modal"> 上加 tabindex="-1" 就能“锁住焦点”,实际它只让这个容器自身可被 .focus() 调用,但完全不影响 Tab 键行为。更糟的是,它可能让容器抢走焦点,导致第一个按钮被跳过。
- 浏览器不会因父元素有
tabindex="-1"就禁止子元素进入 Tab 流 - 背景遮罩层(
<div class="modal-backdrop">)若没设tabindex="-1"且 DOM 位置靠前,有时真会被 Tab 到 - 移动端 Safari 在未触发用户手势前,对非原生可聚焦元素调用
.focus()会静默失败,此时tabindex="-1"存在但无效果
tabindex="-1" 必须加在首个可操作元素上
Modal 打开后,焦点要落到第一个可交互项(如“确认”按钮或 <input>),这个元素必须有 tabindex="-1",否则 .focus() 可能被忽略(尤其 SSR 渲染后 hydration 阶段)。
- 原生可聚焦元素(
<button>、<input>)默认可聚焦,但加tabindex="-1"是跨浏览器保险做法 - 不要加在标题、描述文字等非可操作元素上——它们不是响应点,加了反而让键盘用户停在空白处
- 若首个元素是自定义组件(如
<div role="button">),必须同时有tabindex="-1"和role="button",否则.focus()失效且语义缺失
Tab 键循环必须手动拦截,tabindex 不参与计算
浏览器不会自动把 Tab 键限制在 Modal 内。你得监听 focusin,检测焦点是否移出,再手动拉回。
使用 PptxGenJS 和 Python 解析,将结构化的 HTML/CSS 幻灯片内容转换为原生、可编辑的 PowerPoint(.pptx)演示文稿。
立即学习“前端免费学习笔记(深入)”;
- 监听 Modal 根元素的
focusin事件,检查!modalEl.contains(e.target) - 若移出,立即获取所有可聚焦子元素:
modalEl.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])') - 聚焦第一个(Shift+Tab 时聚焦最后一个):
focusable[0].focus()或focusable[focusable.length - 1].focus() - 不要给 Modal 内所有元素统一加
tabindex="0"——这会引入多余停靠点,降低导航效率
关闭 Modal 后焦点必须回退到触发源
焦点不能消失或跳到 <body>,否则键盘用户会迷失。这和 tabindex 的值无关,但依赖你在打开时记录触发元素。
- 推荐方式:打开 Modal 前用
document.activeElement记录,关闭后调用triggerEl.focus() - 若触发源是动态生成的(如列表中某行的“编辑”按钮),建议传参保存引用,而非仅存 selector 字符串
- 注意:禁用的
<button disabled>自动脱离 Tab 流,无需额外处理tabindex,但需确保它仍是有效的 focus 目标(即未被display: none或inert掩盖)
真正容易被忽略的是:焦点陷阱不是一次设置就能一劳永逸的逻辑,它必须随 Modal 的 mount / unmount 生命周期同步管理,且在 SSR hydration 后、移动端首次用户手势前、以及 DOM 动态插入新控件时都可能失效——这些边界场景比基础实现更常出问题。


















