I have a single table with rows that are grouped, and there should be a header above each group. What is the correct markup for this when it comes to semantics and accessibility?
I'm aware of the scope
attribute for <th>
elements, and I'm thinking the rowgroup
and colgroup
might be related to this, but I don't understand how to actually apply it properly, or whether the <td>
or <tbody>
elements need some attributes applied to them as well.
Here is an example table, without any accessibility attributes:
table { border-collapse: collapse; text-align: center }
thead { background: #ccc }
th, td { padding: 0.25em 0.5em; border: 1px solid #ddd }
tbody th { background: #eee }
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
</tr>
</thead>
<tbody>
<tr>
<th colspan="3">Group 1</th>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</tbody>
<tbody>
<tr>
<th colspan="3">Gruppe 2</th>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</tbody>
<tbody>
<tr>
<th colspan="3">Gruppe 3</th>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</tbody>
</table>
scope="rowgroup"
would be the correct markup.
You already did a great job and used several <tbody>
elements to group rows.
Don’t forget that a table also needs an accessible name, which you can provide by means of aria-label
or <caption>
.
The HTML Spec mentions for rowgroup
:
The row group state means the header cell applies to all the remaining cells in the row group. A
th
element'sscope
attribute must not be in the row group state if the element is not anchored in a row group.
So how exactly does one mark up a row group?
ARIA in HTML mentions that the the default element for the rowgroup
role is <tbody>
, as it’s already used in the question.
<tbody>
<tr>
<th colspan="3" scope="rowgroup">Group 2</th>
</tr>
The row group concept in the HTML specification further mentions that also <thead>
and <tfoot>
establish such row groups.
The next question, then, is how browsers and screen readers actually expose this rowgroup. For concepts like section
roles, the boundaries are announced, so when you navigate inside a new group, its name is announced. rowgroup
is derived from section
, so this behaviour would seem appropriate as well.
I doubt that a lot of screen readers actually announce this. I will look into this, if anybody has results to share already, that would be great.
rowheader
for the <th>
. NVDA 2023.1 then does not announce it.Here’s a sandbox for testing:
<table>
<caption>Table for testing row group headings</caption>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
</tr>
</thead>
<tbody>
<tr>
<th colspan="3" scope="rowgroup">Group 1</th>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</tbody>
<tbody>
<tr>
<th colspan="3" scope="rowgroup">Group 2</th>
</tr>
<tr>
<td>1</td>
<td>2</td>
</tr>
</tbody>
</table>