
Structuring Accessible Data Tables with Caption Scope and Thead
Early web developers famously overused <table> elements for page layout, which led some developers to avoid tables altogether—even when rendering real tabular data like pricing matrices, benchmark results, or schedules.
When displaying two-dimensional data, a semantic <table> is essential. However, a table made only of <tr> and <td> tags leaves screen-reader users unable to tell which column or row header belongs to a given cell.
Example
<!-- Avoid: Ambiguous table cells with no header associations -->
<table>
<tr><td>Browser</td><td>Grid Support</td></tr>
<tr><td>Chrome</td><td>Yes (v57+)</td></tr>
</table>
<!-- Prefer: Explicit caption, thead/tbody, and scope attributes -->
<table>
<caption>CSS Grid Browser Support Matrix</caption>
<thead>
<tr>
<th scope="col">Browser</th>
<th scope="col">Grid Support</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Chrome</th>
<td>Yes (v57+)</td>
</tr>
</tbody>
</table>
Input : Raw benchmark or feature comparison table
Output : Screen-reader friendly semantic HTML table with responsive horizontal scrolling.
Implementation
<div class="table-responsive" role="region" aria-labelledby="benchmark-caption" tabindex="0">
<table class="data-table">
<caption id="benchmark-caption">
Algorithm Time Complexity Comparison
</caption>
<thead>
<tr>
<th scope="col">Algorithm</th>
<th scope="col">Best Case</th>
<th scope="col">Average Case</th>
<th scope="col">Space Complexity</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Two Sum (Hash Table)</th>
<td><code>O(n)</code></td>
<td><code>O(n)</code></td>
<td><code>O(n)</code></td>
</tr>
<tr>
<th scope="row">Binary Search</th>
<td><code>O(1)</code></td>
<td><code>O(log n)</code></td>
<td><code>O(1)</code></td>
</tr>
<tr>
<th scope="row">Bubble Sort</th>
<td><code>O(n)</code></td>
<td><code>O(n^2)</code></td>
<td><code>O(1)</code></td>
</tr>
</tbody>
</table>
</div>
.table-responsive {
overflow-x: auto;
border: 1px solid #dedede;
}
.data-table {
width: 100%;
border-collapse: collapse;
text-align: left;
}
.data-table caption {
font-weight: 700;
font-size: 18px;
padding: 14px 16px;
text-align: left;
color: #082840;
}
.data-table th,
.data-table td {
padding: 12px 16px;
border-top: 1px solid #dedede;
}
.data-table thead th {
background-color: #f6f9fb;
color: #082840;
}
Main Point: Using <th scope="col"> for column headers and <th scope="row"> for the first cell in each row causes screen readers to announce both the column name and row name as the user navigates across cells.
Credits
Photo by Carlos Muza