auditmysitev2.0.2

Missing main heading (h1)

headings/h1-missing

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
medium
axe-core rule
headings/h1-missing

Why we flag this

Screen reader users cannot find the start of the main content via heading navigation.

No <h1> element in the document.

What the rule checks

Scope. Documents with at least one rendered heading.

Expectation. Flags a document that has rendered headings but no rendered level-1 heading: neither an h1 nor role="heading" aria-level="1". A hidden h1 (hidden attribute) does not count. A level-1 heading inside an open shadow root counts. In each viewport the rendered h1 may be a different element. Not a WCAG failure (no criterion requires a level); a best-practice violation for the whole document, without an element.

Source. Best practice, no WCAG failure: axe page-has-heading-one; WAI tutorial Page Structure: Headings

Detection: shared; needs dom, computed_style.

Failing example

The check flags this for review. Corpus case structure_content_model. The page may contain further findings for other rules.

structure_content_model.htmlHTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Index of tool specifications – Acme Hardware</title>
    <style>
        body { font: 16px/1.5 sans-serif; margin: 0 16px; }
        a { display: inline-block; min-width: 44px; min-height: 44px; line-height: 44px; }
        input, button { min-width: 44px; min-height: 44px; }
    </style>
</head>
<body>
    <!-- Product data from a page builder (#783). The feature list wraps its
         items in a div, which the <ul> content model does not allow; the
         browser repairs the tree, the markup stays wrong. The definition list
         groups name-value pairs in div elements, which <dl> does allow, and
         the table is well-formed. -->
    <a href="#main" class="skip-link">Skip to content</a>
    <header>
        <p>Acme Hardware</p>
    </header>
    <nav aria-label="Main">
        <ul>
            <li><a href="/">Home</a></li>
            <li><a href="/tools/">Tools</a></li>
        </ul>
    </nav>
    <main id="main">
        <!-- The page's h1 is hidden; the visible outline starts at h2. -->
        <h1 hidden>Cordless drill CD-18</h1>
        <p class="product-name">Cordless drill CD-18</p>
        <h2>Features</h2>
        <ul id="features">
            <div class="feature-row">
                <li>Two speeds</li>
                <li>LED work light</li>
            </div>
        </ul>
        <h2>Specifications</h2>
        <dl id="specs">
            <div><dt>Voltage</dt><dd>18 V</dd></div>
            <div><dt>Weight</dt><dd>1.4 kg</dd></div>
        </dl>
        <h2>Spare parts</h2>
        <table id="parts">
            <caption>Spare parts and prices</caption>
            <thead>
                <tr><th scope="col">Part</th><th scope="col">Price</th></tr>
            </thead>
            <tbody>
                <tr><td>Battery 2 Ah</td><td>39 EUR</td></tr>
                <tr><td>Charger</td><td>29 EUR</td></tr>
            </tbody>
        </table>
    </main>
    <footer>
        <p>Acme Hardware, Berlin</p>
    </footer>
</body>
</html>

Passing example

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

landmark_main_in_shadow_root.htmlHTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Main landmark inside a shadow root corpus case</title>
</head>
<body>
    <!-- The page's landmarks live in an open shadow root, as in web-component
         app shells. Chrome's accessibility tree exposes the main landmark; a
         document.querySelectorAll('main') check does not see it and used to
         report a missing main. -->
    <app-shell></app-shell>
    <script>
        const root = document.querySelector('app-shell').attachShadow({ mode: 'open' });
        root.innerHTML =
            '<header><nav aria-label="Site"><a href="/">Home</a></nav></header>' +
            '<main><h1>Content in a shadow root</h1><p>Text.</p></main>' +
            '<footer>Footer</footer>';
    </script>
</body>
</html>

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