tabindex仅安全使用0和-1;正整数如1必须删除,否则破坏键盘导航;仅对非原生可聚焦但需键盘访问的容器(如role="button"的div)加0;原生元素加0冗余且干扰SSR;加0后须手动监听Enter/Space并设对应role;装饰性元素禁用tabindex;-1仅用于JS focus()且目标需提前设置;隐藏元素focus失败;Safari不支持focus({preventScroll:true});tabindex="1"导致焦点顺序断裂且不可维护;重排视觉顺序应优先用CSS而非tabindex;隐藏/禁用元素须用inert或移除tabindex+aria-hidden。

tabindex 不是用来“调顺序”的,能安全用的只有 tabindex="0" 和 tabindex="-1";所有正整数(如 tabindex="1")必须立刻删除,否则键盘用户和屏幕阅读器会同步迷失。
哪些元素必须加 tabindex="0",哪些绝对不能加
只对默认不可聚焦、但业务上又必须被键盘访问的容器加 tabindex="0",比如 <div role="button">、<h3 role="tab"> 或自定义折叠面板标题栏。原生可聚焦元素(<button>、<a>、<input>)加了反而冗余,还可能干扰 SSR/hydration 一致性。
- 加了
tabindex="0"后,元素只是“能被 Tab 到”,不会自动响应 Enter/Space —— 必须监听keydown,且只对event.key === 'Enter'或event.key === ' '(空格字符)做处理 - 必须同步设
role,例如role="button",否则屏幕阅读器读作“普通容器”,用户无法理解用途 - 纯装饰性元素(如图标
<svg>容器、<span class="icon">)加了只会制造无效停靠点,应直接移除
tabindex="-1" 的唯一合法用途:让 JS 能 focus(),但用户不能 Tab 到
tabindex="-1" 不是“隐藏开关”,而是动态组件中接管焦点的入口。模态框打开后调 element.focus() 却没反应?大概率是目标按钮是 <div>,没提前设 tabindex="-1";而原生 <button> 根本不需要它 —— 加了反而主动踢出 Tab 流。
- 模态框首次打开时,应立即对第一个可操作项(如“确认”按钮)调用
.focus(),该按钮需在 HTML 中就带tabindex="-1" - 元素若被
display: none或visibility: hidden隐藏,.focus()会静默失败,哪怕有tabindex="-1" -
focus({ preventScroll: true })在 Safari 中不支持,传对象参数会失效;生产环境建议先检测:if ('preventScroll' in FocusOptions.prototype)
为什么 tabindex="1" 是最危险的写法
它不会让元素变成“第一个被 Tab 到的”,只会把它塞进所有正数里按升序排,且所有正数元素都会排在原生可聚焦元素之后 —— 导致导航路径断裂。更糟的是,在 React/Vue 动态渲染或 SSR hydration 场景下,这个值根本不可维护。
立即学习“前端免费学习笔记(深入)”;
- 多个
tabindex="1"元素中,浏览器只认 DOM 中第一个,其余被忽略 - 混用
tabindex="1"和tabindex="2"后,焦点顺序变成“正数升序 → DOM 顺序 →tabindex="0"元素”,完全脱离用户预期 - 如果必须重排视觉顺序(比如表单提交按钮要放在最后但 DOM 在开头),优先用 CSS
order或 Flex/Grid 重排,而非靠tabindex="99"强行干预
隐藏或禁用元素必须真正脱离焦点流
仅用 opacity: 0 或 position: absolute; left: -9999px 隐藏的元素,仍会被 Tab 到。这不是样式问题,是焦点流未切断。
- 临时移出 Tab 流,应显式移除
tabindex,或设为tabindex="-1",再配合aria-hidden="true"(注意二者语义需一致) - 现代方案可用
inert属性,但需检查兼容性;inert会同时阻断焦点、事件和辅助技术访问 - 给整个
display: grid容器加tabindex="-1",会导致所有子元素(哪怕有tabindex="0"或是原生<button>)都被跳过 —— 浏览器会跳过该节点及其子树的默认 Tab 流



















