How to Format Lists Accessibly: Complete Guide (2026)
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: nonecauses Safari + VoiceOver to stop announcing list semantics. Addingrole="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. Supportsstart,reversed, andtypeattributes. 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-setsizeon 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— itslistandlistitemrules 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.
| Approach | Screen-Reader Announcement | SEO / AI Extraction | WCAG 1.3.1 |
|---|---|---|---|
<ul> + <li> | "List with N items" + "X of N" per item | Eligible for bulleted snippets and AI citations | Pass |
<ol> + <li> | "List with N items" + numbered position per item | Eligible for "How to" numbered snippets and step citations | Pass |
<div>s styled like a list | No structure — read as continuous text | Treated as paragraphs; rarely extracted as a list | Fail |
<br>-separated text | One paragraph; no item count, no skip shortcut | Treated as one block of prose | Fail |
<ul> with list-style: none (no role="list") | Correct in most readers; Safari + VoiceOver drops the role | Same as <ul> for SEO/AI | Partial — 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.
