HTML Guides for li
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 aria-setsize attribute tells assistive technologies how many items exist in a set of related elements (such as list items or tree items). It is particularly useful when not all items in a set are present in the DOM — for example, in virtualized lists, paginated results, or lazy-loaded content. According to the WAI-ARIA 1.2 specification:
Authors MUST set the value of aria-setsize to an integer equal to the number of items in the set. If the total number of items is unknown, authors SHOULD set the value of aria-setsize to -1.
This means -1 is a specifically recommended value for cases where the total count of items is indeterminate. The W3C validator’s pattern matching expects only non-negative digits, so it rejects the leading - character. This is a known validator bug and does not reflect an actual problem with your HTML.
Why -1 matters for accessibility
When building interfaces with dynamic or partially loaded content, screen readers need to communicate the size of a set to users. If you have a list of search results but don’t know the total count, setting aria-setsize="-1" tells assistive technologies that the set size is unknown. Without this, a screen reader might announce incorrect or misleading information about how many items exist.
What you should do
Do not change your markup to work around this validator error. The value -1 is correct and serves an important accessibility purpose. Removing it or replacing it with an arbitrary positive number would degrade the experience for users of assistive technologies.
Examples
Valid usage that triggers the false positive
This markup is correct but will be flagged by the validator:
<ul>
<li role="option" aria-setsize="-1" aria-posinset="1">Result A</li>
<li role="option" aria-setsize="-1" aria-posinset="2">Result B</li>
<li role="option" aria-setsize="-1" aria-posinset="3">Result C</li>
</ul>
Here, the total number of results is unknown (perhaps they are loaded on demand), so aria-setsize="-1" correctly signals this to assistive technologies.
Known set size (no validator error)
When the total number of items is known, use the actual count. This will not trigger the validator error:
<ul>
<li role="option" aria-setsize="5" aria-posinset="1">Item 1</li>
<li role="option" aria-setsize="5" aria-posinset="2">Item 2</li>
<li role="option" aria-setsize="5" aria-posinset="3">Item 3</li>
</ul>
When aria-setsize is not needed
If all items in the set are present in the DOM, you don’t need aria-setsize at all — the browser can compute the set size automatically:
<ul role="listbox">
<li role="option">Apple</li>
<li role="option">Banana</li>
<li role="option">Cherry</li>
</ul>
In summary, if you see this validator error and you’re intentionally using aria-setsize="-1" because the total item count is unknown, your code is correct. You can safely ignore this particular warning.
The <li> element has an implicit ARIA role of listitem, and the WHATWG HTML specification restricts which roles can be applied to it. The button role is not among the roles permitted on <li>. When you set role="button" on a <li>, you’re telling assistive technologies that the element is a button, but the browser and the spec still recognize it as a list item. This creates a semantic conflict that can confuse screen readers and other assistive tools, leading to a degraded experience for users who rely on them.
Beyond the validation error, there are practical accessibility concerns. A real <button> element comes with built-in keyboard support (it’s focusable and activatable with Enter or Space), whereas a <li> with role="button" lacks these behaviors by default. You would need to manually add tabindex, keyboard event handlers, and focus styling—effectively recreating what <button> gives you for free. This is error-prone and violates the ARIA principle of preferring native HTML elements over ARIA role overrides.
How to Fix
There are several approaches depending on your use case:
- Place a <button> inside each <li> — This is the best approach when you have a list of actions, as it preserves list semantics while providing proper button functionality.
- Use <button> elements directly — If the items aren’t truly a list, drop the <ul>/<li> structure and use <button> elements instead.
- Use a <div> or <span> with role="button" — If you cannot use a native <button> for some reason, these elements accept the button role. You’ll also need to add tabindex="0" and keyboard event handling yourself.
Examples
❌ Invalid: role="button" on <li> elements
<ul>
<li role="button">Copy</li>
<li role="button">Paste</li>
<li role="button">Delete</li>
</ul>
This triggers the validation error because <li> does not permit the button role.
✅ Fixed: Using <button> elements inside <li>
<ul>
<li><button type="button">Copy</button></li>
<li><button type="button">Paste</button></li>
<li><button type="button">Delete</button></li>
</ul>
This preserves the list structure while providing proper, accessible button behavior with no extra work.
✅ Fixed: Using standalone <button> elements
If the list structure isn’t meaningful, remove it entirely:
<div>
<button type="button">Copy</button>
<button type="button">Paste</button>
<button type="button">Delete</button>
</div>
✅ Fixed: Using a toolbar pattern
For a group of related actions, the ARIA toolbar pattern is a great fit:
<div role="toolbar" aria-label="Text actions">
<button type="button">Copy</button>
<button type="button">Paste</button>
<button type="button">Delete</button>
</div>
✅ Fixed: Using role="button" on a permitted element
If you truly cannot use a native <button>, a <div> or <span> can accept the button role. Note that you must manually handle focus and keyboard interaction:
<div role="button" tabindex="0">Copy</div>
However, this approach is almost always inferior to using a native <button> and should only be used as a last resort. Native elements provide keyboard behavior, form integration, and consistent styling hooks that are difficult to replicate reliably.
The value group is not a valid value for the role attribute on an li element according to the W3C HTML specification.
The role attribute defines the semantic purpose of an element for assistive technologies. Common valid ARIA roles for li elements are listitem, not group. The role group is intended for container elements such as ul, ol, or div when grouping related widgets, not for individual list items.
To fix this, remove the role="group" attribute from the li element.
Incorrect:
<ul>
<li role="group">Item 1</li>
<li role="group">Item 2</li>
</ul>
Correct:
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
For most cases with HTML lists, native semantics suffice and no role attribute is needed for li.
The textbox ARIA role identifies an element that allows free-form text input. While it can technically be applied to elements using contenteditable, it should not be placed on elements that already carry strong semantic meaning, such as <li>. A list item is expected to be a child of <ul>, <ol>, or <menu>, and its implicit listitem role communicates its purpose within a list structure to assistive technologies. Assigning role="textbox" to an <li> overrides this semantic, confusing screen readers and other assistive tools about whether the element is a list item or a text input field.
This is problematic for several reasons:
- Accessibility: Screen readers rely on roles to convey the purpose of elements to users. An <li> with role="textbox" sends mixed signals — it exists within a list structure but announces itself as a text input.
- Standards compliance: The ARIA in HTML specification restricts which roles can be applied to specific elements. The li element does not allow the textbox role, which is why the W3C validator flags this as an error.
- Browser behavior: Browsers may handle the conflicting semantics unpredictably, leading to inconsistent experiences across different user agents.
The best approach is to use native HTML form elements whenever possible. The <input type="text"> element handles single-line text input, and the <textarea> element handles multi-line input. These native elements come with built-in keyboard support, focus management, and form submission behavior — none of which you get for free with a role="textbox" on a non-form element.
If you genuinely need an editable area inside a list and cannot use native form elements, nest a <div> or <span> with role="textbox" inside the <li> rather than placing the role on the <li> itself.
Examples
❌ Incorrect: role="textbox" on an li element
<ul>
<li role="textbox" contenteditable="true">Edit this item</li>
<li role="textbox" contenteditable="true">Edit this item too</li>
</ul>
This triggers the validator error because textbox is not a valid role for <li>.
✅ Fix: Use native form elements
The simplest and most robust fix is to use standard form controls:
<ul>
<li>
<label for="item1">Item 1:</label>
<input type="text" id="item1" value="Edit this item">
</li>
<li>
<label for="item2">Item 2:</label>
<input type="text" id="item2" value="Edit this item too">
</li>
</ul>
For multi-line input, use <textarea>:
<ul>
<li>
<label for="note1">Note:</label>
<textarea id="note1">Edit this content</textarea>
</li>
</ul>
✅ Fix: Nest a div with role="textbox" inside the li
If you need a contenteditable area and cannot use native form elements, place the textbox role on a nested element:
<ul>
<li>
<div id="label1">Item 1:</div>
<div
role="textbox"
contenteditable="true"
aria-labelledby="label1"
aria-placeholder="Enter text">
Edit this item
</div>
</li>
</ul>
This preserves the <li> element’s implicit listitem role while correctly assigning the textbox role to a semantically neutral <div>.
✅ Fix: Remove the list structure entirely
If the items aren’t truly a list, consider dropping the <ul>/<li> structure altogether:
<div id="zipLabel">Enter your five-digit zipcode</div>
<div
role="textbox"
contenteditable="true"
aria-placeholder="5-digit zipcode"
aria-labelledby="zipLabel">
</div>
In every case, prefer native <input> and <textarea> elements over role="textbox" with contenteditable. Native elements provide accessible behavior by default, including keyboard interaction, form validation, and proper focus management, without requiring additional ARIA attributes or JavaScript.
When the HTML parser reaches a </li> closing tag, it expects all elements nested within that list item to already be closed. If any child element remains open, the browser must guess where to close it, which can lead to an unexpected DOM structure. This error typically occurs when a closing tag is accidentally omitted, misspelled, or placed in the wrong order.
This matters for several reasons. First, browsers may interpret the intended structure differently, causing inconsistent rendering across platforms. Second, assistive technologies like screen readers rely on a well-formed DOM to convey content correctly — unclosed elements can confuse the reading order or grouping of content. Third, unclosed elements can cascade into other validation errors, making it harder to identify the real problems in your markup.
Common causes of this error include:
- Forgetting to close an inline element like <span>, <a>, <strong>, or <em> inside a list item.
- Forgetting to close a block-level element like <div> or <p> inside a list item.
- Nesting elements in the wrong order, so closing tags don’t match their opening tags.
- Typos in closing tags (e.g., </sapn> instead of </span>).
To fix the issue, locate the <li> mentioned in the error and check every element opened inside it. Make sure each one has a corresponding, correctly spelled closing tag, and that they are closed in the reverse order they were opened (last opened, first closed).
Examples
Missing closing tag on an inline element
❌ Invalid: The <span> is never closed before </li>.
<ul>
<li>
<span>Example text
</li>
</ul>
✅ Valid: The <span> is properly closed.
<ul>
<li>
<span>Example text</span>
</li>
</ul>
Missing closing tag on a link
❌ Invalid: The <a> element is left open.
<ul>
<li>
<a href="/about">About us
</li>
<li>
<a href="/contact">Contact</a>
</li>
</ul>
✅ Valid: Both <a> elements are closed before their parent </li>.
<ul>
<li>
<a href="/about">About us</a>
</li>
<li>
<a href="/contact">Contact</a>
</li>
</ul>
Multiple unclosed nested elements
❌ Invalid: Both <strong> and <a> are left open inside the <li>.
<ol>
<li>
<a href="/sale"><strong>Big sale
</li>
</ol>
✅ Valid: Nested elements are closed in reverse order (innermost first).
<ol>
<li>
<a href="/sale"><strong>Big sale</strong></a>
</li>
</ol>
Misspelled closing tag
❌ Invalid: The closing tag </sapn> doesn’t match <span>, so the <span> remains open.
<ul>
<li>
<span>Item one</sapn>
</li>
</ul>
✅ Valid: The closing tag is spelled correctly.
<ul>
<li>
<span>Item one</span>
</li>
</ul>
Incorrectly ordered closing tags
❌ Invalid: The </em> and </strong> tags are closed in the wrong order, leaving <em> effectively unclosed when </strong> is reached.
<ul>
<li>
<strong><em>Important note</strong></em>
</li>
</ul>
✅ Valid: Closing tags are in the correct reverse order.
<ul>
<li>
<strong><em>Important note</em></strong>
</li>
</ul>
Many HTML elements have built-in (implicit) ARIA roles defined by the WAI-ARIA specification. The <li> element natively carries the listitem role when it is a child of a <ul>, <ol>, or <menu> element. Adding role="listitem" explicitly doesn’t change behavior, but it clutters your markup and signals a misunderstanding of how semantic HTML and ARIA interact. This falls under the first rule of ARIA use: “If you can use a native HTML element with the semantics and behavior you require already built in, do so, instead of re-purposing an element and adding an ARIA role.”
Redundant ARIA roles create several problems:
- Maintenance burden — Extra attributes add noise to your code, making it harder to read and maintain.
- Potential confusion — Other developers may wonder if the explicit role was added intentionally to override something, leading to uncertainty during code reviews.
- Validator warnings — Tools like the W3C HTML Validator flag these redundancies, and accumulating unnecessary warnings can obscure real issues that need attention.
The ARIA listitem role is designed for situations where you cannot use semantic HTML — for instance, when you need to create a list-like structure from generic elements like <div> or <span>. In those cases, you would pair role="list" on the container with role="listitem" on each child. But when you’re already using <ul>, <ol>, or <menu> with <li> children, the ARIA roles are built in and should not be repeated.
To fix this, simply remove the role="listitem" attribute from your <li> elements. If you also have role="list" on a <ul> or <ol>, remove that too — it’s equally redundant.
Examples
❌ Redundant role on <li> elements
<ul role="list">
<li role="listitem">Apples</li>
<li role="listitem">Bananas</li>
<li role="listitem">Cherries</li>
</ul>
Both role="list" on the <ul> and role="listitem" on each <li> are unnecessary because these elements already carry those roles implicitly.
✅ Clean semantic HTML without redundant roles
<ul>
<li>Apples</li>
<li>Bananas</li>
<li>Cherries</li>
</ul>
The <ul> and <li> elements provide all the accessibility semantics needed without any explicit ARIA attributes.
✅ Using ARIA roles on non-semantic elements (when necessary)
If for some reason you cannot use native list elements, ARIA roles are appropriate on generic elements:
<div role="list">
<div role="listitem">Apples</div>
<div role="listitem">Bananas</div>
<div role="listitem">Cherries</div>
</div>
This is the intended use case for role="listitem" — adding list semantics to elements that don’t have them natively. However, using semantic <ul>/<ol> with <li> is always preferred when possible.
Ready to validate your sites?
Start your free trial today.