About This Accessibility Rule
Table headers play a critical role in making data tables understandable. They label rows and columns so users can interpret the data within each cell. When a table header is empty — containing no visible text — it creates confusion for everyone. Sighted users see a blank cell where a label should be, and screen reader users hear nothing meaningful when navigating to that header, making it difficult or impossible to understand the relationship between the header and its associated data cells.
Screen readers rely on table headers to announce context as users navigate through cells. For example, when a screen reader user moves between cells in a column, the column header is announced to remind them which column they’re in. If that header is empty, the user loses that context entirely.
This rule is identified as empty-table-header in axe-core and is classified as a Deque Best Practice. It primarily affects users who are blind or have low vision and rely on screen readers, but it also impacts sighted users who depend on clear visual labels to understand table data.
Why visible text matters
It’s important to note that this rule specifically checks for visible text. Using only aria-label or aria-labelledby on an empty <th> does not satisfy this rule. While those attributes may provide a name for assistive technology, they don’t help sighted users who also need to see the header text. The goal is to ensure that the header’s purpose is communicated visually and programmatically.
How to fix it
-
Add visible text to every
<th>element so it clearly describes the row or column it represents. -
If the cell isn’t a header, change it from a
<th>to a<td>. This is common for empty corner cells in tables where row and column headers intersect. -
Avoid using only ARIA attributes like
aria-labelon an otherwise empty header. Always include visible text content.
Examples
Incorrect: empty table header
The <th> element has no text content, leaving both sighted and screen reader users without context.
<table>
<tr>
<th></th>
<th>Q1</th>
<th>Q2</th>
</tr>
<tr>
<th>Revenue</th>
<td>$100k</td>
<td>$150k</td>
</tr>
</table>
Incorrect: table header with only aria-label
While aria-label provides a name for assistive technology, there is no visible text for sighted users.
<table>
<tr>
<th aria-label="Student Name"></th>
<th aria-label="Grade"></th>
</tr>
<tr>
<td>Alice</td>
<td>A</td>
</tr>
</table>
Correct: table headers with visible text
Each <th> contains visible text that clearly describes its column.
<table>
<tr>
<th>Student Name</th>
<th>Grade</th>
</tr>
<tr>
<td>Alice</td>
<td>A</td>
</tr>
</table>
Correct: using <td> for a non-header cell
When the top-left corner cell of a table isn’t functioning as a header, use <td> instead of an empty <th>.
<table>
<tr>
<td></td>
<th>Q1</th>
<th>Q2</th>
</tr>
<tr>
<th>Revenue</th>
<td>$100k</td>
<td>$150k</td>
</tr>
</table>
Correct: visually hidden text for special cases
In rare cases where a header needs visible text for assistive technology but the visual design calls for no visible label, you can use a CSS visually-hidden technique. Note that this is a compromise — visible text is always preferred.
<table>
<tr>
<th>
<span class="visually-hidden">Category</span>
</th>
<th>Q1</th>
<th>Q2</th>
</tr>
<tr>
<th>Revenue</th>
<td>$100k</td>
<td>$150k</td>
</tr>
</table>
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
Learn more:
Help us improve our guides
Detect accessibility issues automatically
Rocket Validator scans thousands of pages with Axe Core and the W3C Validator, finding accessibility issues across your entire site.