默认可tab到的元素包括、、、、;非原生交互容器如需加tabindex="0"并满足role、键盘事件、focus样式三条件;不可聚焦,须用绝对定位的/替代;svg/canvas热区需设tabindex="-1"并js管理焦点;隐藏元素仅display: none或aria-hidden="true"且无tabindex才脱离tab流。

哪些元素默认能被 Tab 到,哪些必须手动加 tabindex
原生语义元素如 <button></button>、<a></a>、<input>、<select></select>、<textarea></textarea> 默认就在 Tab 流中,不需要也不应该加 tabindex="0"。加了反而可能在 SSR/hydration 场景下导致焦点错位,或干扰 disabled 状态的自然行为。
只有视觉上可交互但非原生聚焦的容器才需要加 tabindex="0",比如:<div role="button">、自定义 tab 标签、卡片式操作区等。但必须同步满足三个条件:<ul>
<li>
<code>role 属性明确(如 role="button" 或 role="link")
keydown 并处理 Enter 和 Space(注意 Space 需 event.preventDefault() 防止滚屏):focus-visible 样式,且不能被全局 * { outline: none } 覆盖图片地图(
<area> 元素本身**不可聚焦**,即使设置了 tabindex="0" 也无效——浏览器不支持对 <area> 调用 .focus(),也无法通过 Tab 键进入。这是规范限制,不是 bug。
要让图片地图区域可键盘访问,唯一可行路径是放弃纯 <map></map> 方案,改用语义化替代:
- 用
<button></button>或<a></a>叠在图像上方,配合position: absolute和精确坐标定位 - 每个按钮/链接需有
aria-label描述其功能(如aria-label="查看产品详情"),不能只靠视觉位置暗示 - 确保所有叠加元素按逻辑顺序排列在 DOM 中,避免
z-index错乱导致焦点跳过 - 禁用
<img usemap>的href属性,防止点击穿透冲突
动态生成的可点击区域(如 SVG 图标组、Canvas 热区)如何接管焦点
SVG 内部的 <g></g>、<path></path> 或 Canvas 绘制的热区,默认完全不在焦点流里。想支持键盘导航,必须走 JS 主动聚焦路径,且目标元素需设 tabindex="-1"。
关键步骤包括:
- 为每个可交互 SVG 元素添加
tabindex="-1"和role="button"(或对应语义) - 监听容器的
focusin事件,用container.contains(document.activeElement)判断焦点是否还在内部 - 方向键导航时,缓存当前焦点索引,避免每次重新遍历 DOM;
ArrowDown/ArrowRight切到下一个,Home/End直接跳首/末项 - 模态弹出或面板展开后,立即对第一个可聚焦子项调用
.focus({ preventScroll: true });Safari 不支持该选项,需先检测:if ('preventScroll' in FocusOptions.prototype)
隐藏或禁用的可点击区域仍被 Tab 到?检查这些地方
仅用 opacity: 0、visibility: hidden、position: absolute; left: -9999px 隐藏的元素,依然会被 Tab 键捕获。真正脱离焦点流的方式只有两种:
-
display: none—— 元素从渲染树移除,focus()会失败,Tab 键自动跳过 -
aria-hidden="true"+ 移除tabindex—— 仅适用于纯装饰性区域,且必须确保无交互逻辑残留
特别注意:禁用状态(如 disabled 的 <button></button>)天然不进 Tab 流,但用 div[aria-disabled="true"] 模拟禁用时,必须同时移除 tabindex="0",否则用户仍会 Tab 到一个“看起来不能点”的东西上。











