auditmysitev2.0.2

Data table without header cells

tables/header-missing

WCAG success criterion
1.3.1 (level A)
Outcome class
Hint only. An automated check that by design reports review hints only: its findings are marked for manual review, never as confirmed violations.
Area
Accessibility, Structure & Semantics
Default severity
high
axe-core rule
tables/header-missing

Why we flag this

Screen reader users hear cell values without their row or column label.

No <th> and no cell with role="columnheader"/"rowheader".

What the rule checks

Scope. Every rendered, not aria-hidden table: <table> or role table/grid, not presentational.

Expectation. A table without any header cell (<th>, role columnheader/rowheader) is a failure only for a data table whose headers are visual (F91); a layout table or a table without visible headers is allowed. Markup cannot tell which, so the rule reports REVIEW at the table, never a confirmed violation. Presentational, hidden and aria-hidden tables are not judged.

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 flags this for review. Corpus case misc_content_checks. The page may contain further findings for other rules.

misc_content_checks.htmlHTML
<!DOCTYPE html>
<html lang="en" xml:lang="de">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=3">
    <title>Misc content checks corpus case</title>
    <style>
        .low-contrast-checkbox {
            border: 1px solid #f8f8f8;
            background: #ffffff;
        }
        /* Controls sized to 44px: this case is not about target size. */
        button, input { min-width: 44px; min-height: 44px; }
        .inline-link {
            color: blue;
            text-decoration: none;
        }
    </style>
</head>
<body>
    <main>
        <h1>Image maps, tables, abbreviations, and misc issues</h1>

        <!-- area-alt -->
        <img src="/floorplan.png" usemap="#floor-map" alt="Floor plan">
        <map name="floor-map">
            <area href="/room1" shape="rect" coords="0,0,50,50">
        </map>

        <!-- input-image-alt -->
        <input type="image" src="/submit.png">

        <!-- presentation-semantic-children: dropped in #697 — role="presentation" does not
             strip the semantics of descendants (WAI-ARIA 1.2), so this is no finding -->
        <div role="presentation">
            <button>Semantic button inside a presentation container</button>
        </div>

        <!-- tables/header-missing: data table without any headers -->
        <table>
            <tbody>
                <tr><td>Alice</td><td>90</td></tr>
                <tr><td>Bob</td><td>85</td></tr>
            </tbody>
        </table>

        <!-- redundant-entry: two email fields without reuse/autocomplete -->
        <form>
            <label for="email-a">Email</label>
            <input type="email" id="email-a" name="email_a">
            <label for="email-b">Confirm email</label>
            <input type="email" id="email-b" name="email_b">
        </form>

        <!-- abbreviations -->
        <p><abbr>W3C</abbr> publishes web standards.</p>

        <!-- modern-attribute-misuse: popovertarget references a missing element -->
        <button popovertarget="missing-popover-id">Open</button>

        <!-- non-text-contrast-css: checkbox border barely distinguishable from background -->
        <input type="checkbox" class="low-contrast-checkbox">

        <!-- link-in-text-block: inline link distinguished only by color -->
        <p>Read our <a href="/policy" class="inline-link">privacy policy</a> for details.</p>
    </main>
</body>
</html>

Passing example

The check passes here and reports nothing. Corpus case content_tables_ok. The page may contain further findings for other rules.

content_tables_ok.htmlHTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Library statistics</title>
    <style>
        nav a, .skip-link { display: inline-block; min-width: 44px; min-height: 44px; }
    </style>
</head>
<body>
    <a class="skip-link" href="#main">Skip to main content</a>
    <header>
        <p>City Library</p>
        <nav aria-label="Main"><a href="/" aria-current="page">Home</a> <a href="/catalogue">Catalogue</a> <a href="/visit">Visit</a></nav>
    </header>
    <main id="main">
        <h1>Library statistics</h1>
        <table id="prices">
            <caption>Copy and print prices</caption>
            <thead><tr><th scope="col" id="price-item">Item</th><th scope="col" id="price-cost">Cost</th></tr></thead>
            <tbody>
                <tr><td headers="price-item">Black and white page</td><td headers="price-cost">0.10</td></tr>
                <tr><td headers="price-item">Colour page</td><td headers="price-item price-cost">0.50</td></tr>
            </tbody>
        </table>
        <h2 id="hours-heading">Opening hours</h2>
        <table id="hours" aria-labelledby="hours-heading">
            <tr><th scope="row">Monday</th><td>9:00 to 18:00</td></tr>
            <tr><th scope="row">Sunday</th><td></td></tr>
        </table>
        <table id="minimal" aria-label="Late returns">
            <tr><th id="late-head">Late returns this week</th><td></td></tr>
        </table>
        <table id="layout" role="presentation">
            <tr><td>Address: 1 Market Square</td><td>Phone: 555 0100</td></tr>
        </table>
        <div role="table" id="aria-branches" aria-label="Branches">
            <div role="row"><div role="columnheader">Branch</div><div role="columnheader">Status</div></div>
            <div role="row"><div role="cell">North</div><div role="cell">Open</div></div>
        </div>
        <stat-table></stat-table>
        <script>
            customElements.define('stat-table', class extends HTMLElement {
                connectedCallback() {
                    this.attachShadow({ mode: 'open' }).innerHTML =
                        '<table id="shadow-stats"><caption>Visitors</caption>' +
                        '<tr><th scope="col" id="shadow-day">Day</th><th scope="col">Visitors</th></tr>' +
                        '<tr><td headers="shadow-day">Monday</td><td>310</td></tr></table>';
                }
            });
        </script>
    </main>
    <footer><p>City Library, 1 Market Square</p></footer>
</body>
</html>

This page is generated from the rule catalogue and the rule contracts; see therule overview for how the outcome classes are derived.