TaskBuild a 2-year quarterly comparison. Top header row: empty cell, then <th colspan='2' scope='colgroup'>2025</th>, <th colspan='2' scope='colgroup'>2026</th>. Second header row: Region, Q1, Q2, Q1, Q2 — all scope='col'. One body row: <th scope='row'>EU</th> + four data cells.
Complex tables: colspan, rowspan, multi-level headers
100 XP9 min
Theory
When one cell spans multiple columns or rows
<table>
<thead>
<tr>
<th></th>
<th colspan="2" scope="colgroup">2025</th>
<th colspan="2" scope="colgroup">2026</th>
</tr>
<tr>
<th scope="col">Region</th>
<th scope="col">Q1</th>
<th scope="col">Q2</th>
<th scope="col">Q1</th>
<th scope="col">Q2</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">EU</th>
<td>120</td><td>150</td><td>200</td><td>240</td>
</tr>
</tbody>
</table>scope=colgroup / scope=rowgroup
When a header spans multiple columns (year heading over four quarter columns), use scope="colgroup" instead of scope="col". Same for rowgroup when a row header spans multiple body rows.
colspan and rowspan
<td colspan="3">Spans 3 columns</td> <td rowspan="2">Spans 2 rows</td>
Used in headers AND data cells. Spanning data: think "merged cells" in Excel.
Empty top-left corner
When the very top-left cell of a table-with-row-headers is intentionally empty, just <th></th> is fine. Don't put a description there — screen readers will read it. Empty cell signals "no header for this column."
🔒
Sign up to start coding
Theory is open to everyone. The interactive editor, live preview, and check are unlocked with a 7-day free trial — card required, cancel anytime.
Sign up — free trial →First 10 lessons in each track are free. No card needed for those.