How to Format Lists Accessibly: Complete Guide (2026)

Saar Twito9 min read
Saar Twito
Saar TwitoFounder & SEO Engineer

Hi, I'm Saar - a software engineer, SEO specialist, and lecturer who loves building tools and teaching tech.

View author profile →

What Is an Accessible List?

An accessible list uses the semantic HTML elements <ul> (unordered), <ol> (ordered), and <li> (list item) so screen readers can announce the list type, the total item count, and each item's position. Visually styling <div> or <p> elements to look like a list strips away every one of those signals.

Key Facts (TL;DR)

  • WCAG 2.2 SC 1.3.1 Info and Relationships (Level A): list relationships must be programmatically determinable — the visual appearance of bullets is not enough.
  • Screen-reader announcement: a proper <ul> is read as "List with 5 items" on entry; each <li> is read as "1 of 5," "2 of 5," and so on. None of that fires for a styled <div>.
  • The Safari/VoiceOver gotcha: list-style: none causes Safari + VoiceOver to stop announcing list semantics. Adding role="list" on the <ul> restores them.
  • SEO: Google extracts featured snippets and AI Overview bullets directly from semantic <ol> and <ul> markup. A "How to" page with a real <ol> is far more likely to win the snippet than one with line-broken paragraphs.
  • Required nesting: <li> must be a direct child of <ul>, <ol>, or <menu>. Any other parent is invalid HTML.
  • Business impact: ~20% of users who rely on assistive technology cite confusing structure as a top frustration (WebAIM Screen Reader User Survey #10, 2024). Lists are one of the structures that breaks first.

Think of a list element the way you think of the "ingredients" heading in a recipe. Strip the heading and you still have a wall of words — but no one can find the part they came for. <ul> and <ol> are that heading, but for the structure underneath.

Why Accessible Lists Matter

  • Screen-reader context: entering a <ul>in NVDA produces "List with 5 items." That single phrase tells the user how much content is coming, whether to skip it, and where they are when they navigate inside it.
  • Item position: each <li>is announced with its index ("1 of 5," "2 of 5"). Without semantic markup, a user near the bottom has no idea how much further the list goes.
  • Skip-by-list navigation: screen-reader users can jump past a list with one keystroke. <br>-separated paragraphs offer no such shortcut.
  • SEO and AI search: Google's featured-snippet extractor and the citation extractors used by ChatGPT, Perplexity, and Google AI Overviews all parse list semantics. A page with proper <ol>for "How to do X" is dramatically more likely to be cited as bullets in an AI answer.
  • Nested structure: outline relationships (sub-steps, sub-categories) only survive when they are expressed as nested <ul>/<ol>. Indenting <div>s with CSS communicates nothing to assistive tech.

The Three List Elements — and How Each Behaves

HTML provides three list types. Choose the one that matches the relationship between items.

  • <ul> (unordered list): bullets. Order does not change the meaning. Examples: features, ingredients, key points.
  • <ol> (ordered list): numbers. Order is meaningful. Supports start, reversed, and type attributes. Examples: steps, ranked results, instructions.
  • <li> (list item): one entry. Must be a direct child of <ul>, <ol>, or <menu>.
  • <dl> (description list): term/description pairs (covered in a separate article).
<!-- Bad: <br>-separated text. No list semantics at all. -->
<p>
  Apples<br>
  Oranges<br>
  Bananas
</p>

<!-- Bad: <div>s styled to look like a list. -->
<div class="list">
  <div>Apples</div>
  <div>Oranges</div>
  <div>Bananas</div>
</div>

<!-- Bad: <li> outside of a <ul> or <ol>. Invalid HTML. -->
<li>Apples</li>
<li>Oranges</li>

<!-- Good: unordered list -->
<ul>
  <li>Apples</li>
  <li>Oranges</li>
  <li>Bananas</li>
</ul>

<!-- Good: ordered list with start attribute -->
<ol start="3">
  <li>Preheat the oven to 180C.</li>
  <li>Mix the dry ingredients.</li>
  <li>Fold in the wet ingredients.</li>
</ol>

<!-- Good: nested list -->
<ul>
  <li>
    Fruits
    <ul>
      <li>Apples</li>
      <li>Oranges</li>
    </ul>
  </li>
  <li>Vegetables</li>
</ul>

<!-- Good: bulletless list with VoiceOver workaround -->
<ul role="list" class="no-bullets">
  <li>Item one</li>
  <li>Item two</li>
</ul>

How to Check Your Lists

  • Greadme deep scan — flags pages where lists are constructed from <br>-separated text or styled <div>s, and pairs each issue with an AI-generated fix or one-click GitHub PR.
  • Greadme crawler scan — checks list semantics across every indexable page; useful for catching templates (FAQs, navigation, recipe steps) that ship without proper <ul>/<ol>.
  • Greadme AI visibility analyzer — shows whether AI search engines extract your "steps" and "features" sections as bullets in citations, which depends on real list markup.
  • Browser developer tools accessibility tree — inspect a list element and confirm the role is "list" with the correct aria-setsize on each item.
  • Screen reader smoke test: listen to NVDA or VoiceOver enter a list — "List with N items" should be the first thing announced.
  • Open-source axe-core — its list and listitem rules fail any <li> outside a list parent, and any list with non-<li> children.

8 Best Practices for Accessible Lists

1. Use Real <ul> and <ol> Elements

The fastest and most durable accessibility win for lists is also the simplest: replace <br>-separated text and <div> stacks with real <ul> or <ol> markup. Every screen reader, browser, and AI extractor benefits from this single change.

2. Match the List Type to the Meaning

Use <ol> when order matters (steps, rankings, sequenced instructions) and <ul> when it does not (features, options, tags). Screen readers announce each appropriately, and AI engines treat numbered steps differently from unordered bullets when extracting answers.

3. Style Without Breaking Semantics

Remove visual bullets with CSS, never by removing the <ul>. list-style: none keeps the list semantically intact in most screen readers — except in Safari + VoiceOver (see the next tip).

.no-bullets {
  list-style: none;
  padding-left: 0;
}

4. Add role="list" for the Safari/VoiceOver Workaround

Safari + VoiceOver has a long-standing behavior where list-style: none causes the list role to be dropped from the accessibility tree. The accepted workaround is the redundant-but-effective role="list" attribute on the <ul>.

<ul role="list" class="no-bullets">
  <li>One</li>
  <li>Two</li>
</ul>

5. Keep <li> as a Direct Child

The HTML specification requires <li> to be a direct child of <ul>, <ol>, or <menu>. Wrapping items in a <div>for layout breaks the parent/child contract that screen readers rely on for "N of M" announcements.

6. Use the start Attribute Instead of Faking Numbers

When an ordered list continues across page breaks or interrupts itself with prose, use <ol start="5"> instead of typing "5. Step name" into a <ul>. The numbers stay accessible and the list type remains correct.

7. Nest Properly for Sub-Items

Sub-items belong inside their parent <li>, not as siblings of it. The nested <ul> goes inside the parent <li>, after its text content.

8. Don't Wrap Short Items in <p>

For short list items, put the text directly inside <li>. Wrapping each one in a <p> adds an extra paragraph announcement on every item — friction with no benefit.

Common Issues With Lists — and Fixes

Problem: Lists Built From <br> Tags

What's happening: a paragraph uses <br> between items so they appear on separate lines. Screen readers read it as one continuous paragraph with no item count or skip-list shortcut.

Fix: replace the paragraph with a <ul> and turn each line into an <li>. No additional CSS is required.

Problem: VoiceOver Stops Announcing "List With N Items"

What's happening: the design removes bullets with list-style: none. Safari + VoiceOver responds by dropping the list role from the accessibility tree.

Fix: add role="list" to the <ul>. It is redundant in every other browser/screen-reader pair and completely correct in Safari + VoiceOver.

Problem: <li> Wrapped in <div> for Layout

What's happening: a developer wraps each <li> in a <div> to apply card styling. The HTML is now invalid, and screen readers can no longer count items reliably.

Fix: apply the card styling directly to <li> with CSS. <li> can be styled exactly like a <div> — there is no reason to wrap it.

Problem: Ordered List Used When Order Does Not Matter

What's happening: a list of features uses <ol> because the design shows numbers. Screen readers announce them as "step 1, step 2," misleading users into thinking the order is meaningful.

Fix: use <ul> and add the numbers visually with CSS counters if needed. Reserve <ol> for genuinely sequential content.

<ul> vs <ol> vs <div>-Based Lists

The difference between a real list and a fake one is invisible to sighted users and dramatic to everyone else.

ApproachScreen-Reader AnnouncementSEO / AI ExtractionWCAG 1.3.1
<ul> + <li>"List with N items" + "X of N" per itemEligible for bulleted snippets and AI citationsPass
<ol> + <li>"List with N items" + numbered position per itemEligible for "How to" numbered snippets and step citationsPass
<div>s styled like a listNo structure — read as continuous textTreated as paragraphs; rarely extracted as a listFail
<br>-separated textOne paragraph; no item count, no skip shortcutTreated as one block of proseFail
<ul> with list-style: none (no role="list")Correct in most readers; Safari + VoiceOver drops the roleSame as <ul> for SEO/AIPartial — fix with role="list"

FAQ

What does a screen reader actually say when entering a list?

On entering a <ul> with five items, NVDA announces "List with 5 items." As the user moves down, it reads each item as "1 of 5," "2 of 5," and so on. JAWS and VoiceOver behave similarly. None of these announcements fire when the "list" is built from <div>s or <br>s.

Is the "list-style-none breaks VoiceOver" bug really real?

Yes. Safari intentionally drops the list role from the accessibility tree when a list has list-style: none applied — a behavior preserved through every recent macOS and iOS release. The community fix is to add role="list" back onto the <ul>, which restores the announcement.

Does the <ul> vs <ol> choice matter for SEO?

Yes. Google's "How to" featured snippets and AI Overview answers preferentially extract numbered <ol> markup for sequential content. Using <ul> for steps reduces the chance of being chosen for that snippet, even if the visual result looks identical.

Can I put a heading or paragraph inside <li>?

Yes — <li> is flow content and can hold paragraphs, headings, nested lists, and most other elements. Use that flexibility for genuinely structured items, but for simple labels just put the text directly in <li> without an extra <p> wrapper.

Is <li> outside a <ul> ever valid?

No. <li> is only valid as a direct child of <ul>, <ol>, or <menu>. A floating <li> is a parser-level error and will break the list and listitem rules in any automated audit.

How do AI search engines like ChatGPT and Perplexity treat lists?

Generative engines parse list semantics to recognize "these are steps," "these are options," or "these are 5 features," and they preserve that structure when citing. A page with proper <ol> markup is more likely to appear in an AI answer as actual bullets — sometimes with a direct citation back to the source — than a page where the same content lives in <br>-separated prose.

What about navigation menus — should those be lists?

Yes. Wrap navigation links in a <ul> inside <nav>. The list provides structure (item count, position), and the <nav> provides the landmark. Apply list-style: none visually and add role="list" for the Safari/VoiceOver fix if the design has no bullets.

Conclusion

Lists are one of the highest-leverage accessibility wins on any page. Switching from <br>-separated text or styled <div>s to real <ul> and <ol> markup hands every screen-reader user item counts and skip shortcuts, hands Google clean snippet candidates, and hands AI engines the structure they need to cite your steps as bullets. Cost: a few minutes per template.

Run a Greadme deep scan to see every page where lists are missing real semantics, and fix them in order of traffic.