Use semantic HTML. Browsers and assistive tech expose built-in roles and relationships for <ul>
, <ol>
, and <li>
. Screen readers typically announce a “list” and the item count, and let users jump between lists and items. When you replace lists with <p>
plus <br>
, you remove that structure and risk failing WCAG 2.2 Success Criterion 1.3.1 (Info and Relationships). The fix is simple: write real lists and let CSS handle presentation.
The core pattern
Reach for <ul>
when order does not matter and <ol>
when it does. Wrap each item in <li>
. This alone gives you accessible names, roles, and relationships without extra attributes. If you later restyle the bullets or numbers, you retain the semantics and navigation.
<ul>
<li>HTML</li>
<li>CSS</li>
<li>JavaScript</li>
</ul>
Don’t override native semantics with ARIA
Avoid adding roles to native lists. For example, <ul role="navigation">
stops being a list in the accessibility tree and becomes a landmark, which removes list behavior for screen reader users. Use a <nav>
element for navigation and keep the inner list as a list. Add ARIA only when you must recreate semantics in non-list containers.
When, and only when, ARIA role="list"
is reasonable
Sometimes a design uses non-list elements for layout but still contains a real list of items. If you cannot change the markup, you may add role="list"
on the container and role="listitem"
on each item to restore semantics. Prefer real <ul>/<ol>
first; use ARIA as a fallback. If you use role="listitem"
, it must be owned by a role="list"
(or role="group"
).
<section role="list">
<div role="listitem">Item 1</div>
<div role="listitem">Item 2</div>
<div role="listitem">Item 3</div>
</section>
Style lists without breaking them
Customize bullets and numbers with CSS, not markup hacks. Use ::marker
to style list markers, including swapping a bullet for an icon or emoji, and keep the content selectable and announced. For complex numbering or sectioned outlines, use CSS counters rather than hard-coding numbers in text. These approaches preserve semantics while giving full visual control.
ul.features ::marker { content: "🔥 "; }
ol.steps { counter-reset: step; }
ol.steps > li { counter-increment: step; }
ol.steps > li::marker { content: counters(step, ".") ". "; }
Ordered vs unordered: decide by meaning
Choose <ol>
when sequence, ranking, or prerequisites matter. Choose <ul>
for collections without inherent order. Do not fake numbers with text or background images; assistive tech will not expose ordering and users cannot rely on keyboard list navigation. Use the semantic element and layer on CSS for appearance.
Menus, listboxes, and “lists of actions” are not lists
Application menus and listboxes are interactive widgets with distinct keyboard and ARIA patterns. Do not repurpose <ul>
as a desktop-style menu unless you implement the corresponding ARIA roles and interactions, or better, use the patterns defined in the WAI-ARIA Authoring Practices. Keep content lists as lists; build widgets as widgets.
Definition lists have a niche
Use <dl>
for name–value pairs such as glossaries. Do not use it to lay out two-column content that is not truly definitional, as behavior and expectations differ across screen readers. If the relationship is not “term defines description,” stick to <ul>
or structured headings and paragraphs.
Copy, paste, and maintenance
Real lists copy and paste cleanly without stray bullet characters, and they convert predictably in Markdown and CMS editors. Fake lists interleave bullets with text nodes, which often paste as literal glyphs and break numbering. Semantic lists reduce the chance of those artifacts and keep the accessibility tree consistent with what users see. (This follows from how user agents expose native list semantics to assistive tech.)
Common pitfalls and fixes
First, avoid <p>
with <br>
to mimic bullets; convert to <ul>/<ol>
with <li>
. Next, do not wrap each <li>
in headings solely to style them; style the <li>
or child spans instead. Finally, do not nest block elements incorrectly in list markers; put structure inside the <li>
, not into the marker itself. These changes preserve valid markup and predictable announcements.
Accessibility checks to run
Verify that assistive tech announces “list” and the correct item count. Confirm that keyboard navigation moves item by item. Ensure you did not add ARIA roles that suppress native list behavior. Map the outcome to WCAG 2.2 SC 1.3.1 so information and relationships survive style changes and alternative renderings.
Quick reference
- Use
<ul>/<ol>
with<li>
for lists; prefer native semantics over ARIA. - Style with
::marker
and CSS counters; avoid hard-coded bullets/numbers. - Do not add
role="navigation"
or other roles to a<ul>
unless you intend to replace its list semantics. Use<nav>
for landmarks. - If you cannot change the DOM, consider
role="list"
/role="listitem"
as a last resort, ensuring correct ownership. - For interactive menus/listboxes, follow WAI-ARIA Authoring Practices instead of generic lists.
Before and after
Bad (fake list)
<p>
• HTML<br>
• CSS<br>
• JavaScript
</p>
Good (semantic list)
<ul class="stack">
<li>HTML</li>
<li>CSS</li>
<li>JavaScript</li>
</ul>
.stack ::marker { content: "• "; } /* change to emoji if you like */
Wrap-up
Start with the correct element, keep semantics intact, and let CSS handle decoration. You give screen reader users list navigation and counts, you satisfy WCAG’s structural requirements, and you keep your code easier to style, copy, and maintain. From there, you can iterate with ::marker
, counters, and component patterns without sacrificing accessibility.
Assumptions: You target modern evergreen browsers where ::marker
and CSS counters are supported; for legacy environments, fall back gracefully with unstyled markers while keeping semantic HTML intact.