遮罩层不应参与tab顺序,正确做法是设tabindex="-1"和aria-hidden="true",确保与dialog同级、不隐藏且z-index生效;dialog需手动聚焦,用focusin事件实现tab循环,关闭后焦点应回到有效触发元素。

遮罩层为什么不能被 Tab 焦点到
遮罩层(overlay)本身不该进入 Tab 顺序,否则键盘用户会卡在“看不见的黑屏”上。常见错误是给它加 tabindex="0" 或漏设 aria-hidden="true"。
正确做法是:tabindex="-1" + aria-hidden="true",让它可被 JS .focus() 但不参与自然 Tab 流。同时确保遮罩层和 dialog 是同级元素(都挂载在 body 下),否则 modal.contains(e.target) 判断会失效。
- 遮罩层 DOM 节点必须设
aria-hidden="true",否则屏幕阅读器可能朗读“空白遮罩” - 不要用
display: none或visibility: hidden隐藏遮罩层——这会让focus()静默失败,iOS Safari 尤其敏感 - 若遮罩层用
position: fixed,需确认父容器没意外触发层叠上下文(比如transform或opacity: 0.99),否则 z-index 再大也盖不住兄弟节点
dialog 元素打开后焦点没进去?
dialog.showModal() 不保证焦点一定落在内部——它只尝试聚焦第一个可聚焦子元素,但若内容异步加载、或首个元素是 div tabindex="-1",焦点就落到 body 上,Tab 键直接逃逸。
必须手动补焦,且时机关键:不能在 showModal() 后立刻查 DOM,而应在 requestAnimationFrame 或 dialog:open 事件里执行。
- 用
modal.querySelector('button, input, select, textarea, [tabindex]:not([tabindex="-1"])')查找真实可聚焦项,别依赖:focusable(Safari 不支持) - 若没找到,给
modal自身设tabindex="-1"后调用.focus(),这是兜底安全做法 - 移动端 iOS Safari 对
.focus()极其苛刻:目标元素不能被overflow: hidden截断,也不能在display: none父容器里
Tab 键在弹窗里不循环,怎么修
浏览器默认 Tab 会一路跑到页面底部,aria-modal="true" 只是语义声明,**完全不管 Tab 行为**。真正的循环必须靠 JS 拦截 focusin 或 keydown。
推荐用 focusin 事件——它捕获所有方式的焦点进入(键盘、鼠标点击、.focus() 调用),比监听 Tab 键更健壮。
- 提前缓存所有可聚焦元素:
const focusables = Array.from(modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')) - 监听
modal.addEventListener('focusin', e => { if (!modal.contains(e.target)) focusables[0]?.focus(); }) - 如果最后一个元素获得焦点后按 Tab,或第一个元素按 Shift+Tab,就阻止默认行为并跳转——但别在
keydown里粗暴拦所有 Tab,会破坏屏幕阅读器快捷键
关闭弹窗后焦点回哪?
关闭时焦点不能丢在 body 或页面顶部,必须回到触发弹窗的那个按钮——否则键盘用户得狂按几十次 Tab 才能回去,体验直接崩掉。
关键不是“记住一个元素”,而是“记住它是否还存在、是否仍可聚焦”。
- 打开前存引用:
const triggerEl = event.target; modal.dataset.triggerId = triggerEl.id;(避免强引用导致内存泄漏) - 关闭时检查:
const el = document.getElementById(modal.dataset.triggerId); if (el && el.offsetParent) el.focus(); - 如果触发元素已销毁或不可见,退回到页面主内容区(如
main或[role="main"]),别硬滚到top - 记得清理事件监听器,尤其是
focusin和keydown,否则多个弹窗叠加时逻辑会串
focus() 可能静默失败;安卓 WebView 可能根本不支持 dialog::backdrop;老版本 JAWS 会忽略 aria-modal。真要上线,得拿真实设备+辅助工具跑一遍,而不是只看 console.log。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











