auditmysitev2.0.2

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.

table_headers_no_data.htmlHTML
<!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.

table_headers_tbody_ignored.htmlHTML
<!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.