html5 默认不聚焦且无焦点围栏,需手动管理:调用 showmodal() 后聚焦首个可聚焦子元素,并拦截 tab 键实现焦点循环,关闭后恢复触发源焦点。

HTML5 <dialog></dialog> 默认不聚焦,必须手动触发
浏览器原生 <dialog></dialog> 打开后不会自动获得焦点,Tab 导航从页面顶部开始,用户按 Tab 会跳过对话框内容——这不是 bug,是规范行为。WAI-ARIA 要求模态对话框必须有明确的焦点管理,但 HTML5 <dialog></dialog> 不内置该逻辑。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 调用
showModal()后立刻调用dialogElement.focus(),但注意:需确保首个可聚焦子元素存在且可聚焦(如input、button、带tabindex="0"的元素) - 更稳妥的做法是聚焦到对话框内第一个可聚焦元素:
dialog.showModal();<br>const firstFocusable = dialog.querySelector('button, input, select, textarea, [tabindex]:not([tabindex="-1"])');<br>if (firstFocusable) firstFocusable.focus(); - 若首个元素是
<input type="hidden">或tabindex="-1",它会被querySelector忽略,避免误聚焦失效
Tab 键无法在 <dialog></dialog> 内循环,得自己拦截 keydown
原生 <dialog></dialog> 没有焦点围栏(focus trap),Tab / Shift+Tab 会直接跳出对话框,进入背景页面或浏览器地址栏——这是最常被吐槽的“模态失效”现象。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 监听
keydown事件,检测Tab键并阻止默认行为:dialog.addEventListener('keydown', (e) => {<br> if (e.key !== 'Tab') return;<br> e.preventDefault();<br> const focusables = Array.from(dialog.querySelectorAll('button, input, select, textarea, [tabindex]:not([tabindex="-1"])'));<br> const focused = document.activeElement;<br> const currentIndex = focusables.indexOf(focused);<br> let nextIndex = e.shiftKey ? currentIndex - 1 : currentIndex + 1;<br> if (nextIndex >= focusables.length) nextIndex = 0;<br> if (nextIndex < 0) nextIndex = focusables.length - 1;<br> focusables[nextIndex].focus();<br>}); - 务必在
showModal()后绑定该监听,关闭时用removeEventListener清理,否则多个对话框叠加会重复绑定 - 别依赖
document.querySelectorAll('body *:focusable')—— 浏览器没这个伪类,要用显式选择器
tabindex="-1" 是焦点控制的关键开关,不是摆设
很多人给 <dialog></dialog> 加 tabindex="-1" 就以为能聚焦,其实它只让元素“可被脚本聚焦”,不改变 Tab 顺序;而 tabindex="0" 才让它进入自然 Tab 流——但对话框本身不该进 Tab 流,应由内部元素承接。
实操建议:
立即学习“前端免费学习笔记(深入)”;
-
<dialog></dialog>标签本身保持无tabindex或显式设为tabindex="-1",仅用于脚本聚焦入口 - 所有可聚焦子元素必须显式声明可聚焦性:表单控件天然支持,
<div> 类容器需加 <code>tabindex="0",禁用状态元素(disabled或aria-disabled="true")不会被纳入焦点循环 - 避免给非交互元素(如纯文本
<p></p>)加tabindex="0",这会破坏语义和读屏体验 - 在
close事件中,将焦点交还给触发打开对话框的元素:const triggerBtn = document.getElementById('open-dialog-btn');<br>dialog.addEventListener('close', () => {<br> triggerBtn?.focus();<br>}); - 如果触发源是动态生成或已销毁(比如列表项被重新渲染),需提前缓存其位置,或 fallback 到页面主区域(如
main元素或h1) - 不要用
document.body.focus()—— 它不触发任何语义焦点,对辅助技术无效
关闭对话框时焦点必须回退,否则键盘用户会迷失
用户点“X”或按 Esc 关闭 <dialog></dialog> 后,焦点默认停留在 document.body 或丢失,下一次 Tab 从头开始,等于把用户丢回页面开头——这对屏幕阅读器用户尤其不友好。
实操建议:
立即学习“前端免费学习笔记(深入)”;
焦点管理不是锦上添花,是模态对话框可用性的底线。浏览器没替你做,就得一行行写清楚谁该聚焦、什么时候聚焦、聚焦丢了怎么找回来。漏掉任意一环,键盘用户就卡住了。











