How to fix: List items must be inside a list
An <li> must sit directly inside a <ul> or <ol>. On its own it loses the list semantics screen readers depend on.
The fix
Place every <li> inside a <ul> or <ol> parent.
Who this affects
An <li> with no list parent is not part of a set, so it carries no position information and has no list container opening or closing around it. A JAWS user hears the text on its own, with no "list, three items" as they enter, no "two of three" as they move, and no way to skip the block with the list keys. On a checkout with numbered steps or a stack of filters, the grouping that is obvious on screen is gone.
Before and after
<li>Item</li> <!-- floating outside a list -->
<ul><li>Item</li></ul>
Where this usually comes from
- A wrapper <div> sitting between the <ul> and its <li> children, usually for layout or an animation container.
- A component that returns an <li> at its root being reused outside a list, for example inside a card grid.
- List items pasted into rich-text CMS content without the surrounding <ul>, often from Word or from a cached fragment.
- A parent given a role that replaces list semantics, such as <ul role="tablist"> or role="menu", while the children stay <li>.
- Template partials that emit the items and the list wrapper separately, so one conditional branch renders items with no wrapper.
Checking it yourself
Select the <li> in Chrome DevTools and check its ancestors in the Accessibility pane for a list container; the computed role is not the tell, because Chrome still reports listitem for an <li> whose parent is a plain <div>. In the console, [...document.querySelectorAll('li')].filter(li => { const p = li.parentElement; if (!p) return true; const role = p.getAttribute('role'); return role ? !['list','presentation','none'].includes(role) : !['UL','OL','MENU'].includes(p.tagName); }) lists the strays, including the <li> children of a <ul role="tablist">, which a check on the tag name alone would pass. With a screen reader, arrowing onto the item should announce it as part of a list with a count.
A scanner finds this one automatically. What it cannot judge, and how the score is built, is on how we test.
Related fixes
Check your site for this issue
Scan any page free against WCAG 2.2 AA and get every automatically detected issue with its fix.