Complex data table
A complex data table is an HTML table whose headers do not cover an entire row or column, typically due to merged cells or sub-groups of headers. The scope attribute is no longer sufficient: each cell must be associated with its headers via id and headers attributes. When possible, splitting the table into several simple tables remains the best solution.
A sales table by quarter with headers "Semester 1" and "Semester 2" merged across two columns each. Visually, the structure is clear. For a screen reader, it's a minefield: it doesn't know which header applies to which cell.
#When does a table become "complex"?
As soon as headers no longer span only the first row and first column. Merged cells with colspan or rowspan, headers nested across multiple levels, thematic sub-groups in the middle of the table: all cases where the scope attribute can no longer express the relationships between headers and data.
RGAA devotes several criteria to this in its theme 5. The mechanism provided by WCAG: place an id on each <th>, then list these identifiers in the headers attribute of each <td>.
<table>
<caption>Quarterly sales by region</caption>
<thead>
<tr>
<th rowspan="2" id="zone">Region</th>
<th colspan="2" id="s1">Semester 1</th>
<th colspan="2" id="s2">Semester 2</th>
</tr>
<tr>
<th id="t1" headers="s1">Q1</th>
<th id="t2" headers="s1">Q2</th>
<th id="t3" headers="s2">Q3</th>
<th id="t4" headers="s2">Q4</th>
</tr>
</thead>
<tbody>
<tr>
<th id="nord" headers="zone">North</th>
<td headers="nord s1 t1">120 €</td>
<td headers="nord s1 t2">145 €</td>
<td headers="nord s2 t3">98 €</td>
<td headers="nord s2 t4">110 €</td>
</tr>
</tbody>
</table>Each "120 €" cell is linked to its three headers: "North", "Semester 1", "Q1". The screen reader can announce the complete context.
#The pitfall: id/headers and real support
The mechanism is sound in theory. In practice, screen reader support remains uneven. JAWS handles it correctly, NVDA gets by depending on versions, VoiceOver long ignored headers. And each cell must be indexed manually. A single identifier error breaks the chain for the entire row.
This is why Access42 recommends always asking yourself before coding: can this table be split into two or three simple tables with scope? The answer is often yes.
Simplifying a table is not an admission of technical weakness. It's a choice that benefits all users, including those with perfect vision but who struggle to read grids with 15 columns on a phone screen.
#In summary
A table is "complex" when scope is no longer sufficient to link headers and data. The id/headers mechanism exists for these cases, but its maintenance is heavy and its support uneven. The best approach: split into simple tables whenever content allows.