( Perceivable / WCAG 1.3.1 )
List contains an element other than li
What is this issue?
A <ul> or <ol> element has one or more direct children that are neither <li> elements nor one of the small set of elements HTML explicitly permits alongside them (<script> and <template>, which render no visible content). Commonly this looks like a <div> inserted between list items as a visual divider, or a wrapper <span> used purely for spacing.
The list’s own semantics (the fact that it’s a list at all, and how many items it contains) are computed by assistive technology directly from its children in the DOM, the tree structure the browser builds from your HTML that assistive technology reads instead of the visual layout. A non-<li> child isn’t just invalid markup; it’s a child the accessibility tree has no defined way to count as part of the list.
Why does this matter?
When a screen reader lands on a list, it typically announces the list role and item count up front, “list, 4 items,” before reading the first item, so the user knows what they’re entering. That count comes from counting the list’s real children. A stray <div> inserted as a divider between two <li> elements either gets silently skipped from the count (making the announced total wrong) or, in some browsers and screen reader combinations, breaks the list role recognition for the whole element, so it isn’t announced as a list at all.
Either outcome removes information a sighted user gets for free just by glancing at the bullets. A shopping list that announces “list, 3 items” when it actually holds 5 makes a user think they’ve reached the end and stop navigating early, missing items a sighted user would have scrolled straight past to see.
Who is affected?
- Screen reader users: hear an item count that doesn’t match the real number of items, or in some cases lose the list announcement altogether, and may stop navigating before reaching every item.
- Cognitive disabilities: users who rely on an accurate up-front count to gauge how much content a section holds get a number that misrepresents the real scope of the list.
What users experience
Grace uses JAWS on Windows to check a coffee shop’s online menu, tabbing into the “Hot drinks” list. JAWS announces “list, 3 items,” but the menu visually shows four drinks: a <div class="menu-divider"> sits between two <li> elements as a decorative line, and JAWS silently excludes it from the count while still reading its (empty) content as noise between items. Grace assumes she’s seen the full list after three items and moves on, missing the fourth drink entirely, which a sighted customer would have seen without any extra effort.
How do I fix it?
Keep only <li> elements as direct children of <ul> or <ol>, and move anything else (dividers, wrapper markup, styling hooks) inside one of the <li> elements themselves. This works because it restores a one-to-one match between the list’s real children and what a screen reader counts as items, so the announced total is accurate again.
If you need a visual divider between groups of items, style it with a CSS border or margin on an existing <li> rather than inserting a separate element into the list. If the divider genuinely needs to exist as its own element for styling reasons, place it outside the <ul> entirely (between two separate lists, or between a list and the next section) rather than as a rogue child inside one.
Code Examples
<ul>
<li>Espresso - $3.50</li>
<div class="menu-divider"></div>
<li>Cappuccino - $4.25</li>
</ul><!-- Method 1: style the divider on an existing li (no extra element) -->
<ul>
<li class="menu-divider-top">Espresso - $3.50</li>
<li>Cappuccino - $4.25</li>
</ul>
<!-- Method 2: if the divider must be its own element, move it outside the list -->
<ul>
<li>Espresso - $3.50</li>
</ul>
<hr class="menu-divider" aria-hidden="true">
<ul>
<li>Cappuccino - $4.25</li>
</ul>Both fixes remove the non-<li> child from inside the <ul>. The first keeps everything in one list and applies the divider styling with a CSS border on the existing <li>; the second is useful when the visual break is meant to separate two genuinely distinct groups, in which case splitting into two real lists is more accurate than faking a single list with a divider inside it.
Common Mistakes
Mistake: “It’s just one wrapper div for spacing, so it can’t be a real accessibility problem.” A single stray element is enough to throw off the announced item count or, depending on the browser and screen reader combination, disqualify the whole element from being recognized as a list role at all. The size of the visual change has no bearing on how strictly the browser’s accessibility tree enforces valid list structure.
Mistake: “CSS display: flex on the ul changes how children need to be structured.” Applying flexbox or grid layout to a <ul> changes how its children are visually arranged; it has no effect on which elements are valid as direct children in the accessibility tree. A <div> between <li> elements is still a structural violation regardless of what layout CSS is applied to the parent.
Mistake: “I’ll just give the div a role of listitem to fix it.” Adding role="listitem" to a <div> does make assistive technology treat it as a list item, and technically resolves this specific check, but it’s solving the wrong problem. If the content genuinely belongs in the list, use a real <li> element instead of reconstructing list semantics with ARIA; ARIA is for cases native HTML can’t cover, not a substitute for the native element that already exists for exactly this purpose.
How RedFlag Detects This
Automated: axe-core rule, runs on every scan. RedFlag calls axe-core’s list rule as part of every scan, restricted to the WCAG 2.0/2.1/2.2 A and AA rule set. The rule inspects every <ul> and <ol> element and flags any with a direct child that isn’t an <li>, a <script>, or a <template> element.
False negative: the rule only checks direct children of a real <ul>/<ol> element; a page that fakes list appearance entirely with styled <div> elements and CSS-generated bullets, using no <ul> or <ol> at all, has nothing for this rule to inspect and passes automated scanning while providing no list semantics whatsoever. False positive: none typical for this check, since verifying every direct child’s tag name against an allowed set is a binary structural comparison. Manual step: for any flagged list, confirm the fix moved the offending content inside an <li> (or out of the list entirely) rather than just adding a role="listitem" override that resolves the check without using real list markup.
Manual Testing
- Open the page in Chrome or Firefox with NVDA or JAWS running on Windows, or VoiceOver on macOS.
- Navigate to the start of the list and listen to the announcement. It should state “list” followed by the correct item count.
- Count the visible items yourself and compare that number to the announced count.
- Move through the list item by item (NVDA/JAWS: down arrow in browse mode; VoiceOver: Control+Option+Right Arrow) and confirm every visible item is announced, in order, with nothing skipped or announced as unrelated noise between items.
- If the announced count doesn’t match the visible item count, or the list role isn’t announced at all, the check fails.
Related WCAG Success Criteria
1.3.1 Info and Relationships: Information, structure, and relationships conveyed through presentation must also be available programmatically. A visually list-like group of items only satisfies this criterion if the underlying markup actually forms a valid list structure a screen reader can compute the same relationships from.
Related Issues
List items are not contained within a list element is the mirror-image failure: instead of a list containing the wrong kind of child, it’s an <li> existing with no list wrapping it at all.
Definition list structure is invalid and Definition list item used outside a dl element are the same pair of failures (wrong children inside the container, and container-less items outside it) applied to <dl>/<dt>/<dd> term-definition structures instead of <ul>/<ol>.
Heading levels are skipped shares the same root cause across a different element family: a structural relationship that’s visually obvious but not correctly represented in the markup a screen reader reads.
Presentational markup used instead of semantic elements is the broader version of this same failure: a <div>-and-<span> list built with CSS instead of <ul>/<ol>/<li> loses the list semantics this page’s checks depend on entirely.
References
- W3C Understanding 1.3.1: Info and Relationships
- MDN: The ul element
- WebAIM: Semantic Structure - Lists
Frequently asked questions
Can I put a nested ul directly inside another ul, without an li wrapper?
No. A nested list must be placed inside one of the parent list li elements, not as a direct sibling of the other li elements. Wrapping the nested ul inside the relevant li keeps it correctly associated with that item instead of breaking the outer list count.
Does a script or template tag inside a ul also break the count?
No. Script and template elements are allowed as direct children of ul and ol because they render no visible content of their own and are excluded from the accessibility tree entirely. They do not count as a stray element for this rule.
Why does the item count matter if I can already see how many items there are?
A sighted user counts visually without thinking about it, but a screen reader user only gets the count the browser computes from the DOM structure. If a non-li element breaks that structure, the announced count no longer matches what a sighted user would count by eye.
Is this the same rule as list items being outside a list entirely?
No, it is the reverse direction of the same family of failures. This rule covers a ul or ol that contains something other than li as a direct child; a separate rule covers li elements that exist with no ul, ol, or menu wrapping them at all.
Does CSS list-style: none change whether this rule applies?
No. Removing bullet markers with CSS only changes the visual presentation; the ul and li elements still carry their list semantics in the DOM, and any non-li direct child still breaks the announced structure the same way.