auditmysitev2.0.2

List with invalid child elements

lists/invalid-structure

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
medium
axe-core rule
lists/invalid-structure

Why we flag this

Screen reader users hear a wrong number of list items or lose content.

Direct children of <ul>/<ol>/role="list" are not <li>/role="listitem".

What the rule checks

Scope. Every rendered, not aria-hidden list: <ul>/<ol> without a foreign role, or an element with role="list".

Expectation. A list whose direct children (seen through <slot> into assigned content) include anything other than list items (<li> or role="listitem" without another role), <script> or <template> fails: the list announces items, and the stray child is read outside the item structure (H48, HTML list content model). Fires once per list, located at the list. A list with another explicit role (none, presentation, menu, listbox) is not a list. Lists not rendered or under aria-hidden="true" are not in the accessibility tree and are not judged.

Source. WCAG 2.2 SC 1.3.1 Info and Relationships (F2, H48); HTML §4.4.5-4.4.10 list content models; WAI-ARIA 1.2 list/listitem/term/definition

Detection: shared; needs dom, computed_style.

Failing example

The check reports a violation here. Corpus case content_lists_broken, element ul#genres. The page may contain further findings for other rules.

content_lists_broken.htmlHTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Reading lists with broken structure</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>Reading lists</h1>
        <h2>Genres</h2>
        <ul id="genres">
            <li>Crime</li>
            <div id="stray-genre">Poetry</div>
            <li>History</li>
        </ul>
        <h2>Steps to join</h2>
        <ol id="join-steps"></ol>
        <h2>Branches</h2>
        <div role="list" id="branch-list">
            <div role="listitem">North branch</div>
            <span id="stray-branch">South branch</span>
        </div>
        <div role="list" id="empty-aria-list"></div>
        <h2>Glossary</h2>
        <dl id="glossary">
            <dt id="term-alone">Interlibrary loan</dt>
        </dl>
        <div role="term" id="aria-term-alone">Reservation</div>
        <h2>Notices</h2>
        <div class="notice"><li id="orphan-item">The north branch is closed on Monday.</li></div>
        <div role="listitem" id="orphan-aria-item">Returns are free of charge.</div>
        <h2>Archived lists</h2>
        <ul id="hidden-broken" hidden><p>Old genre list</p></ul>
        <ol id="styled-away" style="display: none"></ol>
        <ul id="aria-hidden-broken" aria-hidden="true"><p>Decorative bullet strip</p></ul>
        <h2>Widget lists</h2>
        <shelf-list></shelf-list>
        <iframe id="frame-lists" title="Branch news" style="width:400px;height:120px;border:0" srcdoc="<!DOCTYPE html><html lang=&quot;en&quot;><head><title>Branch news</title></head><body><ul id=&quot;frame-list&quot;><li>North</li><p id=&quot;frame-stray&quot;>South</p></ul></body></html>"></iframe>
        <div id="late-list"></div>
        <script>
            customElements.define('shelf-list', class extends HTMLElement {
                connectedCallback() {
                    this.attachShadow({ mode: 'open' }).innerHTML =
                        '<ul id="shadow-shelf"><li>Atlas</li><p id="shadow-stray">Maps</p></ul>';
                }
            });
            document.getElementById('late-list').innerHTML =
                '<ul id="late-empty"></ul><ol id="late-mixed"><li>Atlas</li><div>Maps</div></ol>';
        </script>
    </main>
    <footer><p>City Library, 1 Market Square</p></footer>
</body>
</html>

Passing example

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

content_lists_responsive_role.htmlHTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Responsive list markers</title>
    <style>
        @media (max-width: 600px) { .cards { list-style: none; padding: 0; } }
        @media (min-width: 601px) { .chips { list-style: none; padding: 0; } }
    </style>
    <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>Responsive list markers</h1>
        <ul role="list" class="cards" id="cards"><li>Events</li><li>Courses</li></ul>
        <ol role="list" class="chips" id="chips"><li>Open</li><li>Closed</li></ol>
    </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.