表格行默认不可聚焦,应使用tabindex="0"使其自然进入tab流;操作按钮需作为最后一个子元素且为原生;safari等移动端需用带tabindex="0"的代理按钮替代聚焦。

表格行()本身不可聚焦,必须显式启用
默认情况下,<tr> 是非交互语义元素,浏览器不会将其纳入 Tab 流。即使你给它加了 <code>tabindex="1" 或其他正数,也解决不了根本问题——它依然无法接收焦点,后续的按钮也无法“顺延”获得焦点。真正要做的,是让这一行具备可聚焦能力,且不破坏语义顺序。
- 只用
tabindex="0":这是唯一安全方式,让它按 DOM 位置自然进入 Tab 流
- 不要用
tabindex="-1":它虽能让 JS 聚焦,但不参与 Tab 导航,用户按 Tab 会直接跳过整行
- 避免
tabindex="1"、tabindex="2" 等正数:它们会把所有行强行插到整个页面焦点流最前面,导致按钮、表单控件被挤到最后,键盘用户导航断裂
操作按钮必须紧随其后,并保持原生可聚焦性
焦点从 <tr tabindex="0"> 移出后,下一个被聚焦的元素,必须是该行内逻辑上“紧接着”的操作按钮。这个“紧接着”不是靠 CSS 视觉排列,而是靠 DOM 顺序和可聚焦性保障。
<ul><li>按钮应作为 <code><tr> 的最后一个子元素(比如放在 <code><td> 底部),而非用绝对定位或 flex order 搞乱结构
<li>使用原生 <code><button></button>:它默认可聚焦、有语义、响应 Enter/Space,无需额外 tabindex
若用 <div role="button"> 替代,必须同时加 <code>tabindex="0" + role="button" + keydown 监听,否则键盘用户按到就卡住
动态显示的操作菜单需配合 :focus-within 或显式焦点捕获
很多表格用 :hover 或 :focus-within 控制操作按钮的显示/隐藏。但仅靠 tabindex="0" 在 <tr> 上,不足以保证菜单在键盘聚焦时稳定可见——因为焦点可能瞬间移走,触发样式重置。
<ul><li>推荐用 <code>:focus-within 配合 <tr tabindex="0">:只要行内任意可聚焦子元素(如按钮)获得焦点,整个 <code><tr> 就保持 :focus-within 状态,菜单不闪退
<li>如果菜单是 JS 动态插入(比如点击才渲染),则需在 <code><tr> 获得焦点后,主动调用 <code>button.focus(),此时按钮必须带 tabindex="-1"(否则 .focus() 失效)
禁用 tabindex="-1" 的常见误用:写成 tabindex="-999" 或漏掉,会导致 .focus() 静默失败,菜单打不开
移动端和 Safari 的兼容性陷阱
iOS Safari 默认只允许 <button></button>、<input>、<textarea></textarea>、<select></select> 和 contenteditable 元素参与 Tab 导航。哪怕你在 <tr> 上写了 <code>tabindex="0",它在 Safari 中大概率仍不可聚焦。
- 真实可行的方案:放弃让
<tr> 接收焦点,改用 <code><button type="button" class="row-trigger"></button> 作为每行首列的“行焦点代理”,设 tabindex="0",视觉上隐藏但保留可访问性
- 该代理按钮需监听
keydown,对 Enter/Space 触发与整行等效的操作(如展开详情、选中行)
- 务必配
role="row" 和 aria-labelledby,让屏幕阅读器理解这是“某行的控制入口”,而不是孤立按钮
复杂点不在怎么写 tabindex,而在于判断:这一行到底该由谁代表聚焦?是 <tr> 自己,还是一个语义更清晰的代理按钮?后者在真实项目中更可控、更少兼容性雷区。</tr>
默认情况下,<tr> 是非交互语义元素,浏览器不会将其纳入 Tab 流。即使你给它加了 <code>tabindex="1" 或其他正数,也解决不了根本问题——它依然无法接收焦点,后续的按钮也无法“顺延”获得焦点。真正要做的,是让这一行具备可聚焦能力,且不破坏语义顺序。
- 只用
tabindex="0":这是唯一安全方式,让它按 DOM 位置自然进入 Tab 流 - 不要用
tabindex="-1":它虽能让 JS 聚焦,但不参与 Tab 导航,用户按 Tab 会直接跳过整行 - 避免
tabindex="1"、tabindex="2"等正数:它们会把所有行强行插到整个页面焦点流最前面,导致按钮、表单控件被挤到最后,键盘用户导航断裂
操作按钮必须紧随其后,并保持原生可聚焦性
焦点从 <tr tabindex="0"> 移出后,下一个被聚焦的元素,必须是该行内逻辑上“紧接着”的操作按钮。这个“紧接着”不是靠 CSS 视觉排列,而是靠 DOM 顺序和可聚焦性保障。
<ul><li>按钮应作为 <code><tr> 的最后一个子元素(比如放在 <code><td> 底部),而非用绝对定位或 flex order 搞乱结构
<li>使用原生 <code><button></button>:它默认可聚焦、有语义、响应 Enter/Space,无需额外 tabindex
<div role="button"> 替代,必须同时加 <code>tabindex="0" + role="button" + keydown 监听,否则键盘用户按到就卡住
动态显示的操作菜单需配合 :focus-within 或显式焦点捕获
很多表格用 :hover 或 :focus-within 控制操作按钮的显示/隐藏。但仅靠 tabindex="0" 在 <tr> 上,不足以保证菜单在键盘聚焦时稳定可见——因为焦点可能瞬间移走,触发样式重置。
<ul><li>推荐用 <code>:focus-within 配合 <tr tabindex="0">:只要行内任意可聚焦子元素(如按钮)获得焦点,整个 <code><tr> 就保持 :focus-within 状态,菜单不闪退
<li>如果菜单是 JS 动态插入(比如点击才渲染),则需在 <code><tr> 获得焦点后,主动调用 <code>button.focus(),此时按钮必须带 tabindex="-1"(否则 .focus() 失效)
tabindex="-1" 的常见误用:写成 tabindex="-999" 或漏掉,会导致 .focus() 静默失败,菜单打不开移动端和 Safari 的兼容性陷阱
iOS Safari 默认只允许 <button></button>、<input>、<textarea></textarea>、<select></select> 和 contenteditable 元素参与 Tab 导航。哪怕你在 <tr> 上写了 <code>tabindex="0",它在 Safari 中大概率仍不可聚焦。
- 真实可行的方案:放弃让
<tr> 接收焦点,改用 <code><button type="button" class="row-trigger"></button>作为每行首列的“行焦点代理”,设tabindex="0",视觉上隐藏但保留可访问性 - 该代理按钮需监听
keydown,对 Enter/Space 触发与整行等效的操作(如展开详情、选中行) - 务必配
role="row"和aria-labelledby,让屏幕阅读器理解这是“某行的控制入口”,而不是孤立按钮
复杂点不在怎么写
tabindex,而在于判断:这一行到底该由谁代表聚焦?是 <tr> 自己,还是一个语义更清晰的代理按钮?后者在真实项目中更可控、更少兼容性雷区。</tr>











