
<ul> or <ol> are <li> (plus <script> and <template>). Wrapping list items in <div>s or dropping stray tags between them breaks the list semantics that screen readers and content parsers rely on. Move the extra markup inside the <li> and the problem disappears.What this check actually flags
The HTML content model for <ul> and <ol> is strict: direct children must be <li> elements. The spec also tolerates <script> and <template> because they render nothing, and the axe-core rule this check mirrors (rule id: list) allows exactly those. Everything else placed directly inside a list, a <div>, a <p>, a stray <a>, a <span> separator, is a violation.
The classic offender looks like this:
<ul class="products">
<div class="row">
<li>Product one</li>
<li>Product two</li>
</div>
<span class="divider"></span>
<li>Product three</li>
</ul>I see this constantly in component-based frontends. Someone needs a grid row wrapper or a decorative divider, the list is already there, so the extra element gets jammed between the <li>s. It renders fine visually, which is exactly why nobody catches it until an audit does.
There is a nastier version of the same mistake: the wrapper div arrives via a framework component. A React map that returns <div key=...><li>...</li></div>, a Vue transition-group defaulting to a div inside a ul, a slider library that injects its own wrapper elements into your markup at runtime. The source template looks clean, the rendered DOM does not. Always audit the rendered DOM.
Why broken list structure costs you
Assistive technology loses the plot. Screen readers announce lists as lists: "list, 8 items", then track position as "3 of 8". That announcement is built from the accessibility tree, which is built from your DOM. Non-<li> children can fragment the list, throw off item counts, or split one list into several phantom lists depending on the browser and screen reader pairing. For a navigation menu, that means a user hears a wrong or confusing map of your site. This is a WCAG 1.3.1 (Info and Relationships) failure, and it is the kind that shows up in accessibility complaints, not just tool reports.
Parsers get a worse version of your content. Search engines lift list markup into list featured snippets and use structural HTML to segment and understand content. So do the AI crawlers everyone suddenly cares about. Clean <ul>/<li> structure is trivially machine-readable. A list interleaved with divs and spans forces every parser to guess where items begin and end. Browsers are forgiving because their error recovery is excellent; I would not bet my snippet eligibility on every downstream parser being as generous.
Error recovery can mangle your DOM. With certain invalid nestings, the HTML parser relocates elements while repairing your markup, so the DOM your CSS and JavaScript operate on is not the DOM you wrote. If you have ever lost an hour to a selector that "should" match, invalid nesting is a classic cause.
Wrong versus right, structurally
How to detect it at scale
- W3C Nu HTML Checker: flags every non-
lichild oful/olas a document error. Best for checking a template once you know which one is dirty. - axe DevTools or Lighthouse: the axe
listrule reports these per page under the accessibility audit. Lighthouse runs it out of the box, so you may already have this data in your CI and nobody is reading it. - Screaming Frog: use a Custom Extraction with XPath like
//ul/*[not(self::li or self::script or self::template)]to pull offending elements across an entire crawl. This is my go-to because it turns a per-page accessibility finding into a site-wide inventory grouped by template. - Sitebulb: includes list structure among its accessibility hints during a standard crawl, useful when you want the finding bundled with the rest of an audit.
- Browser console spot check:
document.querySelectorAll("ul > :not(li):not(script):not(template), ol > :not(li):not(script):not(template)")gives you an instant answer on the rendered DOM, which catches the JavaScript-injected wrappers a source-code review misses.
How to fix it, step by step
- Group findings by template. A thousand flagged URLs is usually three components: a nav menu, a card grid, a footer link block. Fix components, not pages.
- Move wrappers inside the li. If a div exists to style or group content, it lives happily inside
<li>:<li><div class="card">...</div></li>. An<li>can contain nearly any flow content, so there is almost never a layout reason the wrapper must sit outside it. - Or promote the wrapper's job onto the li. Often the cleaner move: put the class on the
<li>itself and delete the div. Less markup, same rendering. - Replace decorative separators with CSS. Divider spans between items become
li + li { border-top: 1px solid #e3e5e8; }. Presentational markup out, stylesheet in. - If the content is not actually a list, stop using ul. Some builds use
<ul>as a generic layout hook for things that are not lists at all. Swap it for<div>s and the semantics problem evaporates. - Watch third-party scripts. Slider and carousel libraries that wrap your slides at runtime are repeat offenders. Configure them to use your
<li>s as slides, or hand them divs instead of a list. - Re-verify on the rendered DOM. Re-run the console query and a Lighthouse pass after deploy, not just against source templates.
Reference: what may sit where
| Element | Direct child of ul/ol? | Inside an li? | Notes |
|---|---|---|---|
<li> | Yes | Only via a nested list | The one true child. |
<script>, <template> | Yes | Yes | Render nothing, so the spec and axe both allow them. |
<div>, <span> | No | Yes | Move them one level down; nothing visual changes. |
<a>, <p>, headings | No | Yes | Common in hand-rolled nav menus; wrap each in an li. |
Nested <ul>/<ol> | No | Yes | Sublists go inside the parent li, not beside it. Menus get this wrong constantly. |
DO
- Keep ul/ol children to li, script, and template only
- Put wrapper divs and rich content inside the li
- Nest sublists inside their parent li
- Draw separators with CSS borders or pseudo-elements
- Verify the rendered DOM, since scripts inject wrappers
DON'T
- Wrap groups of li elements in layout divs
- Drop decorative spans or hr tags between items
- Place a nested ul as a sibling of li elements
- Use ul as a generic container for non-list content
- Slap role="presentation" on a real list to silence the checker
What good looks like
Every <ul> and <ol> on the site contains nothing but <li> elements, all styling hooks live on or inside those items, sublists sit within their parent item, and a Screaming Frog extraction for non-li children comes back empty on the rendered DOM. Screen readers announce accurate item counts, the W3C validator stays quiet, and your lists are in the cleanest possible shape for featured snippet extraction. Honestly, of all the audit findings I hand to dev teams, this is one of the most satisfying: the fix is mechanical, the diff is small, and it never regresses if you add the console one-liner to your review checklist.
FAQ
Browsers render my list fine, so does this really matter?
Can I put a div inside an li?
How do I nest a submenu correctly?
<li><a>Products</a><ul>...</ul></li>. Placing the sublist as a direct child of the outer ul is the single most common nesting mistake in navigation markup.Does fixing list markup improve rankings?
My CSS framework requires a wrapper between ul and li. Now what?
One flagged list usually means a dozen quieter template bugs
Structure errors travel in packs: broken lists, orphaned headings, invalid nesting, duplicate IDs. Our advanced audit sweeps the full rendered DOM across your site and hands you a prioritized, component-level fix list.
Claude Vincent is a technical SEO consultant focused on crawlability, rendering, and AI-search visibility. He writes the field guides and case studies at SEO ProCheck, with a bias toward the durable, unglamorous work that decides whether search engines and AI answer engines can actually read and cite a site.
About SEO ProCheck
Technical SEO consulting and GEO strategy with 20 years of enterprise experience. Case studies, resources, and tools for search and AI visibility.
Work With Me
Technical SEO audits, GEO strategy, site migrations, and international SEO. Hourly consulting for teams who need hands-on support, not just reports.







