( Perceivable / WCAG 1.3.1 )

List items are not contained within a list element

ModerateLevel AWCAG 1.3.1 — Info and Relationships

What is this issue?

An <li> element exists in the page with no <ul>, <ol>, or <menu> element anywhere among its ancestors. The element still carries the <li> tag and its default list-item role, but that role has no parent list to belong to.

This most often happens when a <ul> or <ol> wrapper gets accidentally removed or replaced during a refactor: a developer swaps a list container for a <div> for layout reasons and forgets the <li> children inside it now have nothing to attach to. The <li> elements themselves are untouched; only their container changed.

Why does this matter?

A list item’s accessibility only exists in relation to its parent list: “item 2 of 5” is meaningless without a list of 5 to be the second item in. When an <li> has no list ancestor at all, different browsers and screen readers handle the gap inconsistently: some announce the element with no list context whatsoever, some announce a wrong or absent item count, and some drop the list-item role entirely and read the content as plain unstructured text.

Whichever way it fails, the user loses the specific benefit a list provides: knowing they’re looking at one item among a defined, countable set, with a clear boundary for where the set ends. A sighted user sees that boundary in the layout without thinking about it; a screen reader user relies entirely on the underlying markup to convey the same thing.

Who is affected?

  • Screen reader users: hear list-item content with no reliable list context, inconsistent item counts, or content read as plain text with no indication it was ever meant to be part of a set.
  • Cognitive disabilities: users who use the “item X of Y” structure to track progress through a list lose that tracking entirely when the underlying list relationship is missing.

What users experience

Malik uses VoiceOver on his Mac to browse a restaurant’s online menu, navigating by list using the Rotor. He expects “Hot drinks” to open as a countable list of items, but a recent redesign replaced the <ul> wrapper with a styled <div> for a card-based layout, leaving the <li> elements inside it orphaned. VoiceOver announces each drink’s name with no list-item context and no count, so Malik can’t tell from the announcement alone whether he’s looking at three drinks or ten, and has to keep navigating forward uncertainly until the content changes to a different kind of element.

How do I fix it?

Wrap every <li> element inside a real <ul> or <ol> element, somewhere in its ancestor chain. This works because the list-item role an <li> carries by default only becomes meaningful once a screen reader can also compute the list it belongs to and the total count of items in it; restoring the wrapper restores both.

If a <div> wrapper is required around the <li> elements for layout purposes (a grid or flex container, for example), put the <div> inside the <ul>, wrapping each <li>, or make the <ul> itself the flex or grid container directly; either keeps a genuine <ul> ancestor in place. Only reach for role="list" on a non-list element as a last resort, when changing the actual element type genuinely isn’t possible.

Code Examples

Before
<div class="menu-grid">
  <li>Espresso - $3.50</li>
  <li>Cappuccino - $4.25</li>
</div>
After
<ul class="menu-grid">
  <li>Espresso - $3.50</li>
  <li>Cappuccino - $4.25</li>
</ul>

Changing the wrapper’s tag from <div> to <ul> costs nothing visually (CSS classes, grid layout, and styling carry over unchanged), but it restores the list-item semantics for the two <li> elements inside it. Once the <ul> is back, a screen reader can compute an accurate “list, 2 items” announcement again.

Common Mistakes

Mistake: “I switched to a div because I needed CSS grid, and ul doesn’t support that.” A <ul> accepts display: grid or display: flex exactly the same as a <div> does; changing an element’s CSS display type doesn’t require changing its HTML tag. There’s no layout reason to swap a <ul> for a <div>; only styling utilities are affected, and all of them apply equally to list elements.

Mistake: “The li elements still look like a list visually, so it must still work for screen readers.” Visual appearance and accessible structure are computed completely separately. An <li> that still renders with a bullet or list-style marker through inherited CSS gives no guarantee its list-item role is being announced correctly if the actual <ul>/<ol> ancestor is missing from the DOM.

Mistake: “This only matters for the outer list, not for a list nested inside a card component.” Every <li> on the page needs a list ancestor, including ones generated by a reusable card or accordion component that might get dropped into a page without its intended <ul> wrapper. Test components in isolation as well as in their final page context, since a component can pass in one location and fail in another if it assumes a wrapper it doesn’t actually render itself.

How RedFlag Detects This

Custom automated: RedFlag’s own DOM detector, runs on every scan. RedFlag inspects every <li> element on the page and walks up its ancestor chain, flagging any <li> whose nearest list-type ancestor isn’t a <ul>, <ol>, or <menu> element. This check reuses the same rule id as a native axe-core check but runs as RedFlag’s own custom detector rather than calling axe-core for it.

False negative: the detector walks the live DOM at scan time, so an <li> that’s temporarily reparented outside its list during a drag-and-drop reorder interaction, or one inside a web component’s shadow DOM the detector’s ancestor walk doesn’t pierce, can escape detection depending on the implementation. False positive: none typical for this check, since confirming whether any ancestor is a <ul>, <ol>, or <menu> is a straightforward DOM walk with no judgment call involved. Manual step: for any flagged <li>, confirm with a screen reader that the fix restored an accurate item count announcement, not just a technically-present ancestor tag.

Manual Testing

  1. Open the page in Chrome or Firefox with NVDA or JAWS running on Windows, or VoiceOver on macOS.
  2. Navigate to any group of items that’s visually styled to look like a list, card grid, or repeated set of entries.
  3. Listen for a “list” announcement with an accurate item count before the first item is read.
  4. If the items are announced individually with no list context, or the count doesn’t match the number of visible items, inspect the markup in browser dev tools to confirm whether a <ul>, <ol>, or <menu> ancestor is present.
  5. On macOS or iOS, cross-check with VoiceOver’s Rotor set to Lists: a genuinely list-wrapped group appears in that list; an orphaned <li> group does not.

1.3.1 Info and Relationships: Information, structure, and relationships conveyed through presentation must also be available programmatically. A group of items that looks like a list visually only satisfies this criterion when the underlying markup actually forms a valid list an assistive technology can compute the same relationship from.

List contains an element other than li is the mirror-image failure: instead of an <li> missing its list wrapper, it’s a <ul> or <ol> containing a child that isn’t an <li> 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>.

References

Frequently asked questions

Does the li have to be a direct child of ul or ol, or can it be nested inside another element?

The li does not need to be the ul or ol immediate child, only a descendant of one somewhere up the ancestor chain. Wrapping li elements inside a div that sits inside a ul is fine; the failure only happens when no ul, ol, or menu exists anywhere above the li at all.

Does menu count as a valid list container the same as ul and ol?

Yes. The menu element is a valid list container for li children, alongside ul and ol, even though it is used far less often in practice. An li inside a menu with no ul or ol nearby still passes this check.

If I style an li to look like a card instead of a list item, does it still need a list wrapper?

Yes. Visual styling has no effect on the DOM relationship a screen reader reads. An li styled to look nothing like a traditional bulleted item still needs a ul or ol ancestor, or it is still announced incorrectly if it is announced as a list item at all.

Can I fix this by adding role="list" to the li own parent div instead of using a real ul?

Yes, that is a valid ARIA-based fix when you cannot change the parent element type, but a real ul or ol is simpler and more reliable whenever you control the markup directly. Reserve the role="list" override for cases where a div wrapper is required for other reasons.

Is this the same rule as a ul containing something other than li?

No, it is the reverse direction. This rule covers an li that exists without any ul, ol, or menu ancestor at all; a separate rule covers a ul or ol that contains a direct child other than li.