Table header without associated data cells
tables/header-without-data
- WCAG success criterion
- 1.3.1 (level A)
- Outcome class
- Automatic. An automated check that can report a confirmed violation.
- Area
- Accessibility, Structure & Semantics
- Default severity
- high
- axe-core rule
th-has-data-cells
Why we flag this
Screen readers announce a header that structures no data.
Header cell with no associated data cells in the table.
What the rule checks
Scope. Every rendered, not aria-hidden table with header cells.
Expectation. A table with header cells but not a single data cell (<td>, cell, gridcell) has headers that label nothing: violation at each header cell. One data cell, even an empty one, is enough to pass; row headers next to data cells pass (#638). A row group without any row means the rows are still loading or hidden: that is tables/data-undetermined, not this id.
Source. WCAG 2.2 SC 1.3.1 Info and Relationships (H51, H39, H63, H43, F91, F92); HTML §4.9 tabular data; WAI-ARIA 1.2 table/grid/row/cell roles
Detection: shared; needs dom, computed_style.
Failing example
The check reports a violation here. Corpus case table_headers_no_data. The page may contain further findings for other rules.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Table headers without data cells corpus case</title>
</head>
<body>
<main>
<h1>Table with headers but no data rows</h1>
<table aria-label="Empty results">
<thead>
<tr><th>Name</th><th>Score</th></tr>
</thead>
<tbody>
<tr><th>No results</th><th>—</th></tr>
</tbody>
</table>
</main>
</body>
</html>Passing example
The check passes here and reports nothing. Corpus case table_headers_tbody_ignored. The page may contain further findings for other rules.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Data table with thead and tbody corpus case</title>
<style>
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.bar { display: inline-block; width: 4rem; height: .5rem; background: #ddd; }
.bar > span { display: block; height: 100%; background: #333; }
</style>
</head>
<body>
<main>
<h1>Levers by sector</h1>
<!-- #638: Chrome exposes <thead> as rowgroup but marks <tbody> ignored;
its rows and cells (some empty, some starting with an aria-hidden
child) are still the header cells' data cells. -->
<div class="scroll" role="region" aria-label="Lever map" tabindex="0">
<table class="map">
<caption class="sr-only">Lever map: sectors and the levers that apply</caption>
<thead>
<tr><th scope="col">Sector</th><th scope="col" class="num">Share</th><th scope="col">Generate clean</th><th scope="col">Electrify</th></tr>
</thead>
<tbody>
<tr><th scope="row">Power and heat</th><td class="num"><span class="bar" aria-hidden="true"><span style="width:81.8%"></span></span>33 %</td><td><span aria-hidden="true">●</span><span class="sr-only">applies</span></td><td></td></tr>
<tr><th scope="row">Transport</th><td class="num"><span class="bar" aria-hidden="true"><span style="width:40.8%"></span></span>16 %</td><td></td><td><span aria-hidden="true">●</span><span class="sr-only">applies</span></td></tr>
</tbody>
</table>
</div>
</main>
</body>
</html>This page is generated from the rule catalogue and the rule contracts; see therule overview for how the outcome classes are derived.