多级表头必须用<thead>内多行<tr>配合colspan和rowspan实现,禁止嵌套<table>、CSS模拟或混入<tbody>;第一行用colspan划大组、rowspan固定贯穿项,第二行写最底层列头且每个设scope="col",所有行逻辑列数(colspan总和)必须严格一致。

多级表头必须用 <thead> 里多行 <tr> 配合 colspan 和 rowspan 实现,不能嵌套 <table>、不能靠 CSS 模拟、也不能把 <th> 塞进 <tbody> —— 否则结构崩、读屏器错、Excel 导出丢合并。
怎么写第一行和第二行的 <tr> 才不偏移
核心是算清“逻辑列数”:所有 <th> 的 colspan 值加起来必须一致,且等于最终数据列数。比如第一行写了 <th rowspan="2">姓名</th><th colspan="3">Q1 销售</th>,那这一行逻辑列数就是 1 + 3 = 4;第二行就必须提供 4 个单元格(哪怕其中一个是空的占位),例如 <th>线上</th><th>线下</th><th>代理</th> 只有 3 个?浏览器会自动补一个 <th></th>,但 JS 里 rows[1].cells.length 就变成 4,遍历时容易越界。
- 先手动画草图,标出每层要跨几列、几行
- 第一行优先用
colspan划大组(如“2024年”“2023年”),用rowspan固定纵向贯穿项(如“项目”“序号”) - 第二行只写最底层对应数据列的
<th>,每个都带scope="col",不设colspan(除非它还要再分) - 别在第二行漏掉某列——哪怕该列上层没分组,也得显式写个
<th></th>占位
rowspan 跨两行时,第二行对应位置为什么不能写 <th>
rowspan="2" 表示这个 <th> 占据当前行 + 下一行的同一列位置,浏览器会在下一行“跳过”该列索引。如果你在第二行又写了一个 <th>,就相当于强行插入一个新单元格,破坏了逻辑网格对齐,导致后续所有 <td> 列偏移。
- 例如:
<th rowspan="2">姓名</th><th colspan="2">成绩</th>→ 第二行只能写两个<th>(如“语文”“数学”),不能写三个 - 检查方法:打开 DevTools,在 Console 里运行
document.querySelector('table').rows[0].cells.length === document.querySelector('table').rows[1].cells.length,结果必须为true - 常见翻车点:给顶层
<th>加了rowspan,却忘了在下面几行对应列留空
scope 属性填什么才不影响读屏器和 SEO
95% 场景下,只有最底层(即直接对应 <tbody> 某一列)的 <th> 需要设 scope="col",其他层级一律不设或设为空。乱填 scope="colgroup" 或 scope="row" 不仅无效,还会干扰屏幕阅读器判断。
立即学习“前端免费学习笔记(深入)”;
- 所有最终列头(第二行、第三行里实际映射到数据列的
<th>)统一写scope="col" - 顶层分组标题(如“2024年”)不需要
scope,留空即可 - 绝对不要给
rowspan > 1的<th>写scope="row"——它仍是列头,不是行头 - 验证方式:用 VoiceOver 或 NVDA 听一遍,确认每列移动时播报的是该列正确标题,而非重复或跳读
导出 Excel 或打印时合并失效怎么办
Office 和多数 PDF 打印引擎只认标准 DOM 结构,colspan/rowspan 必须合法且自洽。CSS 模拟的“视觉合并”完全无效,JS 动态生成后没校验列数也会崩。
- 导出前务必检查:每一行
<tr>在<thead>中的cells.length是否恒定 - 避免在
<thead>用rowspan跨到<tbody>—— HTML 规范不支持,浏览器会自动补<td>,DOM 已损坏 - 如果用了框架(如 Ant Design Vue 的
a-table),确认它底层渲染出的仍是标准<thead><tr><th>结构,而不是 div 模拟 - 复杂三级表头慎用:屏幕阅读器支持弱,移动端小屏易换行错乱,建议降级为两层 + CSS 分组线辅助
真正麻烦的不是属性怎么写,而是动手前没画草图——少算一格 colspan,整张表头就塌一半。



















