Structure and landmarks Rule

Lists must only contain list items list

A ul or ol tells a screen reader 'list, N items' and lets the person jump item by item. That only works when the children are li elements. A div, p, span or wrapper component dropped straight inside the list breaks the count and can split one list into several, so the structure a sighted reader sees is lost.

Websight reports this as <ul> and <ol> must only directly contain <li>, <script> or <template> elements

Who it affects

Someone who is blind, checking which postcodes an online florist delivers to hears 'list, 2 items' when there are six areas on screen, because the others are wrapped in divs.

Someone who uses a screen reader shortcut to skip past a list lands at the end and misses the items the browser did not count.

Someone using a reader mode or a simplified view sees the areas collapse into a run of words with no bullets.

Broken versus fixed

Both examples are live and rendered in a sandboxed frame. The broken one is deliberately broken.

Broken

Some screen readers announce: list, 2 items. There are six areas on screen; four of them sit inside divs.

Fixed

Announced: list, 6 items. Each area is one item and the person can step through them.

Show the markup behind both examples
Broken
<style>body{font-family:system-ui;margin:0;padding:16px}ul{margin:4px 0 0;padding-left:22px}.area{margin:2px 0}</style>
<p style="margin:0"><strong>Same-day delivery areas</strong></p>
<ul>
  <li>NR1 City centre</li>
  <div class="area"><li>NR2 Golden Triangle</li></div>
  <div class="area"><li>NR3 New Catton</li></div>
  <div class="area"><li>NR4 Eaton</li></div>
  <div class="area"><li>NR5 Costessey</li></div>
  <li>NR6 Hellesdon</li>
</ul>
Fixed
<style>body{font-family:system-ui;margin:0;padding:16px}ul{margin:4px 0 0;padding-left:22px}.area{margin:2px 0}</style>
<p style="margin:0"><strong>Same-day delivery areas</strong></p>
<ul>
  <li class="area">NR1 City centre</li>
  <li class="area">NR2 Golden Triangle</li>
  <li class="area">NR3 New Catton</li>
  <li class="area">NR4 Eaton</li>
  <li class="area">NR5 Costessey</li>
  <li class="area">NR6 Hellesdon</li>
</ul>

Test it by hand in two minutes

  1. Right click the list, choose Inspect, and expand the ul or ol. Every direct child should be an li (script and template are also allowed).
  2. With NVDA or VoiceOver, move to the list. The announced item count should match what you can see.
  3. Turn CSS off (in Chrome DevTools open the Rendering drawer and emulate a print or no-CSS view, or use the Web Developer extension). Each item should still show as a bullet or number on its own line.

The fix

Before
<ul class="areas">
  <div class="area"><li>NR1 City centre</li></div>
  <div class="area"><li>NR2 Golden Triangle</li></div>
</ul>
After
<ul class="areas">
  <li class="area">NR1 City centre</li>
  <li class="area">NR2 Golden Triangle</li>
</ul>

Move the class to the li and drop the wrapper. If your component framework insists on a wrapper, render it inside the li, not around it.

Note placed inside the list

Before
<ul>
  <li>Monday</li>
  <li>Tuesday</li>
  <p>Order by 2pm for same-day delivery.</p>
</ul>
After
<ul>
  <li>Monday</li>
  <li>Tuesday</li>
</ul>
<p>Order by 2pm for same-day delivery.</p>

The note belongs after the list, not in it.

Heading inside the list

Before
<ul>
  <h3>North Norwich</h3>
  <li>NR3 New Catton</li>
</ul>
After
<h3>North Norwich</h3>
<ul>
  <li>NR3 New Catton</li>
</ul>

Headings go before the list they introduce.

Nested list outside its item

Before
<ul>
  <li>Zone 1</li>
  <ul><li>NR1</li></ul>
</ul>
After
<ul>
  <li>Zone 1
    <ul><li>NR1</li></ul>
  </li>
</ul>

A sub-list lives inside the li it belongs to, so screen readers announce it as nested.

Common mistakes

  • Wrapping each li in a div so the framework can attach a key or a class.
  • Putting the heading or intro paragraph inside the ul to keep it in the same box.
  • Nesting a ul directly inside the parent ul instead of inside an li.
  • Replacing the list with divs and typed bullet characters, which loses the list entirely.

WCAG criteria this rule maps to

axe-core tags this rule as WCAG 2.2 level A. It is one of the checks for:

The scanner can see any direct child of a ul or ol that is not li, script or template. It cannot tell whether a group of items that are not in a list at all should have been one. Automated tools find only part of the accessibility problems on a page; a pass on this rule is not a pass on the criterion.

Scan your own page for this and 63 other checks

Open any page in Chrome, click Websight, and get a score out of 100 with every issue explained and a draft fix snippet per element.

Add Websight to Chrome

Free scans, no account, nothing leaves your computer. Automated tools find only part of the problems; the extension says so on every report.