HTML Guides for ul
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
The alert ARIA role is used to communicate important, typically time-sensitive messages to the user. When an element has role="alert", assistive technologies like screen readers will immediately announce its content to the user, interrupting whatever they are currently doing. This makes it ideal for error messages, warnings, or status updates that require immediate attention.
However, not every HTML element can accept every ARIA role. The WHATWG HTML specification and WAI-ARIA in HTML define rules about which roles are allowed on which elements. The <ul> element has an implicit role of list, and the alert role is not among the roles permitted on <ul>. This restriction exists because overriding the semantic meaning of a list element with an alert role creates a conflict — assistive technologies would no longer convey the list structure to users, and the element’s children (<li> elements) would lose their meaningful context as list items.
This matters for accessibility and standards compliance. If a screen reader encounters a <ul> with role="alert", the behavior becomes unpredictable. Some screen readers might announce it as an alert but fail to convey the list structure, while others might ignore the role entirely. Users who rely on assistive technology could miss either the alert or the list semantics, both of which may be important.
The fix depends on your intent. If you need to alert the user about content that happens to include a list, wrap the <ul> in a container element (like a <div>) and apply role="alert" to that container. If the content doesn’t need to be a list, replace the <ul> with a more appropriate element like <div> or <p>.
Examples
❌ Invalid: role="alert" directly on a <ul>
<ul role="alert">
<li>Your password must be at least 8 characters.</li>
<li>Your password must contain a number.</li>
</ul>
This triggers the validation error because alert is not a valid role for the <ul> element.
✅ Fixed: Wrapping the list in a <div> with role="alert"
<div role="alert">
<ul>
<li>Your password must be at least 8 characters.</li>
<li>Your password must contain a number.</li>
</ul>
</div>
Here, the <div> carries the role="alert", so assistive technologies will announce the content immediately. The <ul> retains its native list semantics, and the <li> items are properly conveyed as list items.
✅ Fixed: Using a non-list element when list structure isn’t needed
<div role="alert">
<p>Your session will expire in 2 minutes.</p>
</div>
If your alert content is a simple message rather than a list of items, use a more appropriate element like <p> or <div>.
✅ Fixed: Using aria-live as an alternative for dynamic updates
<div aria-live="assertive" role="alert">
<ul>
<li>Error: Email address is required.</li>
<li>Error: Name field cannot be empty.</li>
</ul>
</div>
The aria-live="assertive" attribute on the wrapper ensures that when the content is dynamically updated, assistive technologies announce the changes immediately. Combined with role="alert" on the wrapper (not the list), this provides robust accessible notifications while preserving list semantics.
Key points to remember
- The role="alert" attribute cannot be placed on <ul>, <ol>, or <li> elements.
- Always apply role="alert" to a generic container element like <div> or <span>.
- If your alert content includes a list, nest the list inside the alert container rather than making the list itself the alert.
- The alert role implicitly sets aria-live="assertive" and aria-atomic="true", so you don’t need to add those separately when using role="alert".
In HTML, every element has a set of ARIA roles it is allowed to carry. The ul element implicitly has the list role, and the ARIA specification only permits certain roles to override it — specifically directory, group, listbox, menu, menubar, none, presentation, radiogroup, tablist, toolbar, and tree. The navigation role is not among them.
The navigation role is a landmark role, meaning it identifies a major section of the page dedicated to navigational links. HTML5 introduced the nav element specifically for this purpose, and it carries the navigation role implicitly — no role attribute needed. When you place role="navigation" on a ul, you’re conflicting with the element’s semantics. A ul represents a list of items, not a navigational landmark. Assistive technologies like screen readers rely on correct role assignments to help users understand page structure and navigate efficiently. An incorrect role can confuse users by misrepresenting what the element actually is.
Beyond accessibility concerns, this is a standards compliance issue. The W3C validator enforces the rules defined in the ARIA in HTML specification, which maps each HTML element to its allowed roles. Violating these rules means your markup is invalid and may behave unpredictably across different browsers and assistive technologies.
The fix is straightforward: use a nav element as the wrapper for your navigation list. This gives you the navigation landmark semantics automatically, while the ul retains its proper list role. Both elements work together — the nav tells assistive technologies “this is a navigation section,” and the ul tells them “here is a list of links.”
Examples
❌ Incorrect: navigation role on a ul
This triggers the validation error because navigation is not an allowed role for ul.
<ul role="navigation">
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
✅ Correct: wrapping the ul in a nav element
The nav element provides the navigation landmark implicitly. No role attribute is needed.
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
✅ Correct: labeling multiple navigation landmarks
When a page has more than one nav element, use aria-label to distinguish them for screen reader users.
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
<nav aria-label="Footer">
<ul>
<li><a href="/privacy">Privacy Policy</a></li>
<li><a href="/terms">Terms of Service</a></li>
</ul>
</nav>
✅ Correct: using an allowed role on ul
If you need the ul to behave as something other than a plain list — for example, a menu in a web application — use one of its permitted roles.
<ul role="menubar">
<li role="menuitem"><a href="/">Home</a></li>
<li role="menuitem"><a href="/about">About</a></li>
<li role="menuitem"><a href="/contact">Contact</a></li>
</ul>
Note that menu and menubar roles are meant for application-style menus with keyboard interaction, not for simple site navigation. For standard website navigation, the nav wrapper approach is almost always the right choice.
The W3C validator raises this error because ARIA roles must be compatible with the element they are applied to. A <ul> element has an implicit ARIA role of list, and overriding it with tabpanel creates a conflict. The tabpanel role signals to assistive technologies that the element is a panel of content activated by a corresponding tab. When this role is placed on a <ul>, screen readers lose the semantic meaning of the list (item count, list navigation, etc.) while also misrepresenting the element’s function in the tab interface.
This matters for several reasons:
- Accessibility: Screen reader users rely on correct roles to navigate and understand page structure. A <ul> marked as tabpanel confuses both its list semantics and its role in the tab interface.
- Standards compliance: The ARIA in HTML specification defines which roles are allowed on which elements. The tabpanel role is not permitted on <ul>.
- Browser behavior: Browsers may handle conflicting roles inconsistently, leading to unpredictable behavior across assistive technologies.
The fix is straightforward: wrap the <ul> inside a proper container element (like a <div> or <section>) and apply the tabpanel role to that container instead.
Examples
Incorrect: tabpanel role on a <ul>
This triggers the validation error because tabpanel is not a valid role for <ul>:
<div role="tablist" aria-label="Recipe categories">
<button role="tab" aria-controls="panel-1" aria-selected="true" id="tab-1">Appetizers</button>
<button role="tab" aria-controls="panel-2" aria-selected="false" id="tab-2">Desserts</button>
</div>
<ul role="tabpanel" id="panel-1" aria-labelledby="tab-1">
<li>Bruschetta</li>
<li>Spring rolls</li>
</ul>
<ul role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
<li>Tiramisu</li>
<li>Cheesecake</li>
</ul>
Correct: tabpanel role on a container wrapping the <ul>
Move the tabpanel role to a <div> and nest the <ul> inside it. This preserves both the tab panel semantics and the list semantics:
<div role="tablist" aria-label="Recipe categories">
<button role="tab" aria-controls="panel-1" aria-selected="true" id="tab-1">Appetizers</button>
<button role="tab" aria-controls="panel-2" aria-selected="false" id="tab-2">Desserts</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
<ul>
<li>Bruschetta</li>
<li>Spring rolls</li>
</ul>
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
<ul>
<li>Tiramisu</li>
<li>Cheesecake</li>
</ul>
</div>
Correct: Using <section> as the tab panel
A <section> element also works well as a tab panel container, especially when the panel content is more complex:
<div role="tablist" aria-label="Project info">
<button role="tab" aria-controls="tasks-panel" aria-selected="true" id="tasks-tab">Tasks</button>
<button role="tab" aria-controls="notes-panel" aria-selected="false" id="notes-tab">Notes</button>
</div>
<section role="tabpanel" id="tasks-panel" aria-labelledby="tasks-tab">
<h2>Current tasks</h2>
<ul>
<li>Review pull requests</li>
<li>Update documentation</li>
</ul>
</section>
<section role="tabpanel" id="notes-panel" aria-labelledby="notes-tab" hidden>
<h2>Meeting notes</h2>
<p>Discussed project timeline and milestones.</p>
</section>
In a properly structured tabbed interface:
- The tablist role goes on the container that holds the tab buttons.
- Each tab trigger gets role="tab" with aria-controls pointing to its panel’s id.
- Each content panel gets role="tabpanel" on a generic container like <div> or <section>, with aria-labelledby referencing the corresponding tab’s id.
- List elements like <ul> and <ol> should remain inside the panel as regular content, retaining their native list semantics.
The HTML specification defines a strict content model for the <ul> (unordered list) element: its permitted content is zero or more <li> elements, optionally intermixed with <script> and <template> elements. A <div> is not among these allowed children, so placing one directly inside a <ul> produces a validation error.
This issue commonly arises when developers try to group or wrap list items for styling or layout purposes. For example, you might want to add a container around certain <li> elements for flexbox or grid alignment, or you might be using a templating system that injects wrapper <div> elements into your list markup.
Why this matters
- Browser parsing inconsistencies: When browsers encounter invalid nesting, they attempt to fix the DOM structure automatically, but different browsers may handle it differently. This can lead to unexpected rendering where list items appear outside their intended container or styles break unpredictably.
- Accessibility: Screen readers and assistive technologies rely on correct semantic structure to convey list relationships to users. A <div> breaking the <ul> → <li> relationship can cause assistive tools to misinterpret or skip list content entirely.
- Standards compliance: Invalid HTML can cause cascading parser errors — note that the validator message says “Suppressing further errors from this subtree,” meaning additional issues within that structure may be hidden from you.
How to fix it
- Move the <div> inside the <li>: If you need a wrapper for styling, place it inside the list item rather than around it.
- Remove the <div> entirely: If it serves no purpose, simply remove it and let the <li> elements sit directly inside the <ul>.
- Use CSS on the <li> elements: In many cases, you can apply the styles you need directly to the <li> elements without an extra wrapper.
- Use role="list" on a <div> parent: If your layout truly requires <div> wrappers, consider restructuring with ARIA roles, though native semantic elements are always preferred.
Examples
❌ Incorrect: <div> as a direct child of <ul>
<ul>
<div>
<li>Apples</li>
<li>Bananas</li>
</div>
<div>
<li>Carrots</li>
<li>Dates</li>
</div>
</ul>
✅ Correct: Move the <div> inside each <li>
<ul>
<li><div>Apples</div></li>
<li><div>Bananas</div></li>
<li><div>Carrots</div></li>
<li><div>Dates</div></li>
</ul>
✅ Correct: Remove the <div> entirely
<ul>
<li>Apples</li>
<li>Bananas</li>
<li>Carrots</li>
<li>Dates</li>
</ul>
❌ Incorrect: Using a <div> as a styling wrapper around list items
<ul class="product-list">
<div class="row">
<li>Product A</li>
<li>Product B</li>
<li>Product C</li>
</div>
</ul>
✅ Correct: Apply layout styles directly to the <ul> and <li> elements
<ul class="product-list row">
<li>Product A</li>
<li>Product B</li>
<li>Product C</li>
</ul>
❌ Incorrect: Template or component wrapper inserting a <div>
This often happens in frameworks where a component renders a wrapping <div>:
<ul>
<div class="list-item-wrapper">
<li>Item 1</li>
</div>
<div class="list-item-wrapper">
<li>Item 2</li>
</div>
</ul>
✅ Correct: Move the wrapper class to the <li> itself
<ul>
<li class="list-item-wrapper">Item 1</li>
<li class="list-item-wrapper">Item 2</li>
</ul>
The same rule applies to <ol> (ordered list) elements — they share the same content model restriction. Always ensure that <li> is the direct child of <ul> or <ol>, and place any additional wrapper elements inside the <li> rather than around it.
According to the HTML specification, the content model for <ul> is strictly limited to zero or more <li> elements. Any text node placed directly inside the <ul> violates this rule, even if it seems harmless or invisible. Browsers may still render the page, but the resulting DOM structure is technically invalid and can lead to unpredictable behavior across different browsers and assistive technologies.
This matters for accessibility because screen readers rely on proper list structure to announce the number of items and allow users to navigate between them. Stray text nodes inside a <ul> can confuse these tools, causing list items to be miscounted or the text to be read in an unexpected context.
There are several common scenarios that trigger this error:
Loose text used as a list title. Developers sometimes place a heading or label directly inside the <ul> to describe the list. This text must be moved outside the list element.
Stray or other entities between list items. This often happens in templating systems or when code is concatenated, where characters or other text nodes end up between <li> elements. These should be removed entirely, since spacing between list items should be controlled with CSS.
Accidentally placing inline content without wrapping it in <li>. Sometimes content that should be a list item is simply missing its <li> wrapper.
Examples
❌ Text used as a list title inside <ul>
<ul>
Fruits
<li>Apple</li>
<li>Orange</li>
<li>Banana</li>
</ul>
The word “Fruits” is a text node directly inside the <ul>, which is not allowed.
✅ Move the title outside the list
<h3>Fruits</h3>
<ul>
<li>Apple</li>
<li>Orange</li>
<li>Banana</li>
</ul>
Using a heading before the list is semantically clear. You can also use a <p> or <span> if a heading isn’t appropriate.
❌ entities between list items
<ul>
<li>First item</li>
<li>Second item</li>
<li>Third item</li>
</ul>
Each is a text node sitting directly inside the <ul>, triggering the error.
✅ Remove the entities and use CSS for spacing
<ul>
<li>First item</li>
<li>Second item</li>
<li>Third item</li>
</ul>
ul li {
margin-bottom: 0.5em;
}
Any visual spacing between list items should be handled with CSS margin or padding, not with HTML entities.
❌ Unwrapped content that should be a list item
<ul>
<li>Milk</li>
Eggs
<li>Bread</li>
</ul>
✅ Wrap the content in an <li> element
<ul>
<li>Milk</li>
<li>Eggs</li>
<li>Bread</li>
</ul>
The same rules apply to <ol> (ordered lists) and <menu> elements — their direct children must be <li> elements, and text nodes are not permitted. If your list is generated dynamically by a templating engine or JavaScript, check the output carefully for stray whitespace or text that may have been injected between list items.
The HTML specification assigns implicit ARIA roles to many elements, meaning browsers and assistive technologies already understand their purpose without any extra attributes. The ul element has a built-in role of list, the nav element has a role of navigation, the button element has a role of button, and so on. When you explicitly add a role that matches the element’s implicit role, it creates redundancy that the validator warns about.
This principle is formalized as the first rule of ARIA use: do not use ARIA if a native HTML element already provides the semantics you need. Adding redundant ARIA roles clutters your markup, can confuse developers maintaining the code, and in rare edge cases may cause assistive technologies to announce information twice or behave unexpectedly.
This same warning applies to other elements with implicit roles, such as adding role="navigation" to a nav element, role="banner" to a header element, or role="contentinfo" to a footer element.
A note about Safari and list-style: none
There is one well-known exception worth mentioning. Safari intentionally removes list semantics from ul and ol elements when list-style: none is applied via CSS. This means VoiceOver on macOS and iOS will not announce the element as a list. In this specific case, some developers deliberately add role="list" to restore the list semantics. While the W3C validator will still flag it as redundant (since it evaluates HTML in isolation, without considering CSS), this is a legitimate accessibility pattern where the redundant role serves a real purpose. If you’re in this situation, you may choose to keep role="list" and accept the validator warning.
Examples
Incorrect: redundant role="list" on ul
<ul role="list">
<li>Apples</li>
<li>Bananas</li>
<li>Cherries</li>
</ul>
Correct: relying on implicit semantics
<ul>
<li>Apples</li>
<li>Bananas</li>
<li>Cherries</li>
</ul>
Incorrect: other common redundant roles
<nav role="navigation">
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<main role="main">
<h1>Welcome</h1>
</main>
<footer role="contentinfo">
<p>© 2024 Example Inc.</p>
</footer>
Correct: native elements without redundant roles
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<main>
<h1>Welcome</h1>
</main>
<footer>
<p>© 2024 Example Inc.</p>
</footer>
Acceptable exception: restoring semantics removed by CSS
If your stylesheet strips list markers and you need to preserve list semantics for screen readers, the redundant role is a pragmatic choice:
<!-- list-style: none is applied via CSS, which removes semantics in Safari -->
<ul role="list" class="unstyled-list">
<li>Step one</li>
<li>Step two</li>
<li>Step three</li>
</ul>
In this case, you can suppress or ignore the validator warning, understanding that it serves an accessibility need that the validator cannot detect from the HTML alone.
Ready to validate your sites?
Start your free trial today.