HTML Guides for aria
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 <main> element represents the dominant, unique content of a document — the primary content that is directly related to or expands upon the central topic of the page. Having more than one visible <main> element creates ambiguity: browsers, screen readers, and other assistive technologies use <main> to identify the primary content area, and multiple visible instances make it unclear which content block is truly the main one.
This is particularly important for accessibility. Screen reader users often rely on landmark navigation to jump directly to the main content of a page. When multiple visible <main> elements exist, this shortcut becomes unreliable or confusing, as the user has no way to know which <main> holds the content they’re looking for.
There are legitimate scenarios where multiple <main> elements make sense — for example, in single-page applications (SPAs) where different views are swapped in and out dynamically using JavaScript. The HTML specification accommodates this by allowing multiple <main> elements as long as only one is visible at a time. The others must be hidden using the hidden attribute.
How to fix it
- If you only need one main content area, remove the extra <main> elements and keep just one.
- If you need multiple views (e.g., for tabbed content or SPA-style navigation), add the hidden attribute to all <main> elements except the one currently active. Use JavaScript to toggle visibility by adding or removing the hidden attribute as needed.
Examples
❌ Invalid: Two visible <main> elements
<header>
<h1>My Website</h1>
</header>
<main>
<h2>Welcome</h2>
<p>This is the home page content.</p>
</main>
<main>
<h2>About</h2>
<p>This is the about page content.</p>
</main>
Both <main> elements are visible, which violates the specification and confuses assistive technologies.
✅ Fixed: Single <main> element
If you don’t need multiple views, simply use one <main>:
<header>
<h1>My Website</h1>
</header>
<main>
<h2>Welcome</h2>
<p>This is the home page content.</p>
</main>
✅ Fixed: Multiple <main> elements with only one visible
If you need multiple views for JavaScript-driven navigation, hide all but the active one using the hidden attribute:
<header>
<nav>
<button onclick="switchView('home')">Home</button>
<button onclick="switchView('about')">About</button>
</nav>
</header>
<main id="home">
<h2>Welcome</h2>
<p>This is the home page content.</p>
</main>
<main id="about" hidden>
<h2>About</h2>
<p>This is the about page content.</p>
</main>
In this pattern, JavaScript would toggle the hidden attribute when the user navigates between views, ensuring only one <main> is ever visible at a time.
❌ Invalid: Using CSS instead of hidden
Note that hiding a <main> element with CSS (e.g., display: none or visibility: hidden) does not satisfy the HTML specification. The validator checks for the hidden attribute, not CSS styles:
<!-- This still triggers the validation error -->
<main>
<h2>Welcome</h2>
</main>
<main style="display: none;">
<h2>About</h2>
</main>
Always use the hidden attribute to indicate that a <main> element is not currently relevant to the page.
An a element with both an href attribute and aria-disabled="true" is invalid; either remove aria-disabled or the href attribute.
The aria-disabled attribute is used for interactive elements to indicate that the element is perceivable as disabled by assistive technologies. However, using aria-disabled="true" in combination with an href attribute on an a element is not valid, because the link remains actionable for both user agents and assistive devices. Instead, if a link should appear disabled, you should remove the href attribute, use CSS for styling, and optionally use aria-disabled="true". If you need the element to always act as a link, avoid aria-disabled and control user access through application logic.
Incorrect:
<a href="page.html" aria-disabled="true">Visit Page</a>
Correct—Option 1: Remove aria-disabled, keep link active
<a href="page.html">Visit Page</a>
Correct—Option 2: Remove href, use aria-disabled, for non-actionable item
<a aria-disabled="true" tabindex="-1" style="pointer-events: none; color: gray;">Visit Page</a>
In the second correct example, setting tabindex="-1" prevents keyboard navigation, and pointer-events: none; makes the link unclickable, while aria-disabled="true" makes the disabled state accessible.
A role="columnheader" element must be a child of or associated with a role="row" element.
In HTML, ARIA roles such as columnheader are used to improve accessibility for assistive technologies. According to the ARIA specification, a columnheader role should appear inside an element with role="row", which itself should be inside an element with role="table" or role="grid". This structure mimics how native tables are constructed with <th> elements inside <tr>s.
Correct structure:
- role="table" or role="grid" contains one or more elements with role="row".
- Each role="row" contains one or more elements with role="columnheader" (or role="cell").
Example using ARIA roles for a simple table:
<div role="table" aria-label="Sample Table">
<div role="row">
<div role="columnheader">Name</div>
<div role="columnheader">Age</div>
</div>
<div role="row">
<div role="cell">Alice</div>
<div role="cell">30</div>
</div>
</div>
Best practice:
Whenever possible, use native table elements, which have built-in roles and accessibility, reducing the chance of ARIA misuse.
Example using native table markup:
<table>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
<tr>
<td>Alice</td>
<td>30</td>
</tr>
</table>
Ensure that any element with role="columnheader" is always contained within a parent with role="row". Avoid placing role="columnheader" directly inside a container without the appropriate role="row" ancestor.
The WAI-ARIA specification defines strict ownership requirements for certain roles. The listitem role is one such role — it must be “owned by” an element with role="list" or role="group". “Owned by” means the listitem must be either a direct DOM child of the owning element, or explicitly associated with it via the aria-owns attribute.
This matters because screen readers and other assistive technologies rely on the accessibility tree to convey structure to users. When a screen reader encounters a properly structured list, it announces something like “list, 3 items” and lets the user navigate between items. Without the parent role="list", the individual items lose their list context — users won’t know how many items exist, where the list begins and ends, or that the items are related at all.
In most cases, the simplest and most robust fix is to use native HTML list elements (<ul> or <ol> with <li> children) instead of ARIA roles. Native elements have built-in semantics that don’t require additional attributes. Only use ARIA roles when native elements aren’t feasible — for example, when building a custom component where the visual layout prevents using standard list markup.
Examples
Incorrect: listitem without a parent list
These listitem elements are not contained within a role="list" or role="group" parent, so the validator reports an error.
<div role="listitem">Apples</div>
<div role="listitem">Bananas</div>
<div role="listitem">Cherries</div>
Correct: wrapping items in role="list"
Adding a parent container with role="list" establishes the required ownership relationship.
<div role="list">
<div role="listitem">Apples</div>
<div role="listitem">Bananas</div>
<div role="listitem">Cherries</div>
</div>
Correct: using native HTML list elements
Native <ul> and <li> elements implicitly carry the list and listitem roles without any ARIA attributes. This is the preferred approach.
<ul>
<li>Apples</li>
<li>Bananas</li>
<li>Cherries</li>
</ul>
Correct: using role="group" for nested sublists
The role="group" container is appropriate for grouping a subset of list items within a larger list, such as a nested sublist.
<div role="list">
<div role="listitem">Fruits
<div role="group">
<div role="listitem">Apples</div>
<div role="listitem">Bananas</div>
</div>
</div>
<div role="listitem">Vegetables
<div role="group">
<div role="listitem">Carrots</div>
<div role="listitem">Peas</div>
</div>
</div>
</div>
Note that role="group" should itself be nested inside a role="list" — it doesn’t replace the top-level list container, but rather serves as an intermediate grouping mechanism within one.
Correct: using aria-owns for non-descendant ownership
If the DOM structure prevents you from nesting the items directly inside the list container, you can use aria-owns to establish the relationship programmatically.
<div role="list" aria-owns="item-1 item-2 item-3"></div>
<!-- These items live elsewhere in the DOM -->
<div role="listitem" id="item-1">Apples</div>
<div role="listitem" id="item-2">Bananas</div>
<div role="listitem" id="item-3">Cherries</div>
This approach should be used sparingly, as it can create confusion if the visual order doesn’t match the accessibility tree order. Whenever possible, restructure your HTML so the items are actual descendants of the list container.
The WAI-ARIA specification defines strict ownership requirements for certain roles. The menuitem role represents an option in a set of choices and is only meaningful when it exists within the context of a menu. When a menuitem appears outside of a menu or menubar, screen readers and other assistive technologies have no way to determine that it belongs to a menu widget. They cannot announce the total number of items, provide keyboard navigation between items, or convey the menu’s hierarchical structure to the user.
This requirement follows the concept of required owned elements and required context roles in ARIA. Just as a <li> element belongs inside a <ul> or <ol>, a menuitem belongs inside a menu or menubar. The relationship can be established in two ways:
- DOM nesting — the menuitem element is a DOM descendant of the menu or menubar element.
- The aria-owns attribute — the menu or menubar element uses aria-owns to reference the menuitem by its id, establishing ownership even when the elements aren’t nested in the DOM.
It’s important to note that ARIA menu roles are intended for application-style menus — the kind you’d find in a desktop application (e.g., File, Edit, View menus). They are not meant for standard website navigation. For typical site navigation, use semantic HTML elements like <nav> with <ul>, <li>, and <a> elements instead.
How to fix it
- Identify every element with role="menuitem" in your markup.
- Ensure each one is contained within an element that has role="menu" or role="menubar", either through DOM nesting or via aria-owns.
-
Choose the correct parent role:
- Use role="menubar" for a persistent, typically horizontal menu bar (like a desktop application’s top-level menu).
- Use role="menu" for a popup or dropdown menu that contains a group of menu items.
- If you’re using menus for site navigation, consider removing the ARIA menu roles entirely and using semantic HTML (<nav>, <ul>, <li>, <a>) instead.
Examples
Incorrect — menuitem without a menu context
This triggers the validator error because the menuitem elements have no parent menu or menubar:
<div>
<div role="menuitem">Cut</div>
<div role="menuitem">Copy</div>
<div role="menuitem">Paste</div>
</div>
Correct — menuitem inside a menu
Wrapping the items in an element with role="menu" resolves the issue:
<div role="menu">
<div role="menuitem" tabindex="0">Cut</div>
<div role="menuitem" tabindex="-1">Copy</div>
<div role="menuitem" tabindex="-1">Paste</div>
</div>
Correct — menuitem inside a menubar
For a persistent horizontal menu bar with application-style actions:
<div role="menubar">
<div role="menuitem" tabindex="0">File</div>
<div role="menuitem" tabindex="-1">Edit</div>
<div role="menuitem" tabindex="-1">View</div>
</div>
Correct — nested menus with dropdown submenus
A menubar with a dropdown menu containing additional menuitem elements:
<div role="menubar">
<div role="menuitem" tabindex="0" aria-haspopup="true" aria-expanded="false">
File
<div role="menu">
<div role="menuitem" tabindex="-1">New</div>
<div role="menuitem" tabindex="-1">Open</div>
<div role="menuitem" tabindex="-1">Save</div>
</div>
</div>
<div role="menuitem" tabindex="-1" aria-haspopup="true" aria-expanded="false">
Edit
<div role="menu">
<div role="menuitem" tabindex="-1">Cut</div>
<div role="menuitem" tabindex="-1">Copy</div>
<div role="menuitem" tabindex="-1">Paste</div>
</div>
</div>
</div>
Correct — using aria-owns for ownership without DOM nesting
When the menuitem elements cannot be nested inside the menu in the DOM (e.g., due to layout constraints), use aria-owns to establish the relationship:
<div role="menu" aria-owns="item-cut item-copy item-paste"></div>
<div role="menuitem" id="item-cut" tabindex="0">Cut</div>
<div role="menuitem" id="item-copy" tabindex="-1">Copy</div>
<div role="menuitem" id="item-paste" tabindex="-1">Paste</div>
Better alternative — use semantic HTML for site navigation
If you’re building standard website navigation (not an application-style menu), avoid ARIA menu roles altogether:
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
This approach is simpler, more accessible by default, and doesn’t trigger the validator warning. Reserve role="menu", role="menubar", and role="menuitem" for true application-style menus that implement full keyboard interaction patterns as described in the ARIA Authoring Practices Guide.
The WAI-ARIA specification defines a strict ownership model for tab-related roles. An element with role="tab" controls the visibility of an associated role="tabpanel" element, and tabs are expected to be grouped within a tablist. This relationship is how assistive technologies like screen readers understand and communicate the tab interface pattern to users — for example, announcing “tab 2 of 4” when focus moves between tabs.
When a tab is not contained in or owned by a tablist, screen readers cannot determine how many tabs exist in the group, which tab is currently selected, or how to navigate between them. This fundamentally breaks the accessibility of the tab interface, making it confusing or unusable for people who rely on assistive technologies.
There are two ways to establish the required relationship:
- Direct containment: Place the role="tab" elements as direct children of the role="tablist" element. This is the most common and straightforward approach.
- Using aria-owns: If the DOM structure prevents direct nesting, add aria-owns to the tablist element with a space-separated list of id values referencing each tab. This tells assistive technologies that the tablist owns those tabs even though they aren’t direct children in the DOM.
Examples
Incorrect: tab outside of a tablist
In this example, the role="tab" buttons are siblings of the tablist rather than children of it, which triggers the validation error.
<div class="tabs">
<div role="tablist" aria-label="Sample Tabs"></div>
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
First Tab
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2">
Second Tab
</button>
</div>
Correct: tabs as direct children of tablist
The simplest fix is to place the role="tab" elements directly inside the role="tablist" element.
<div class="tabs">
<div role="tablist" aria-label="Sample Tabs">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1" tabindex="0">
First Tab
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">
Second Tab
</button>
</div>
<div id="panel-1" role="tabpanel" tabindex="0" aria-labelledby="tab-1">
<p>Content for the first panel</p>
</div>
<div id="panel-2" role="tabpanel" tabindex="0" aria-labelledby="tab-2" hidden>
<p>Content for the second panel</p>
</div>
</div>
Correct: using aria-owns when DOM nesting isn’t possible
If your layout or framework makes it difficult to nest the tabs directly inside the tablist, you can use aria-owns to establish the relationship programmatically.
<div class="tabs">
<div role="tablist" aria-label="Sample Tabs" aria-owns="tab-1 tab-2"></div>
<div class="tab-wrapper">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1" tabindex="0">
First Tab
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2" tabindex="-1">
Second Tab
</button>
</div>
<div id="panel-1" role="tabpanel" tabindex="0" aria-labelledby="tab-1">
<p>Content for the first panel</p>
</div>
<div id="panel-2" role="tabpanel" tabindex="0" aria-labelledby="tab-2" hidden>
<p>Content for the second panel</p>
</div>
</div>
Additional notes
- Each role="tab" element should use aria-selected to indicate which tab is active ("true") and which are not ("false").
- Use aria-controls on each tab to reference the id of its associated tabpanel.
- Use aria-labelledby on each tabpanel to point back to its controlling tab.
- Set tabindex="0" on the active tab and tabindex="-1" on inactive tabs to support keyboard navigation with arrow keys within the tablist.
- Always include an aria-label or aria-labelledby on the tablist to give it an accessible name.
Hidden inputs are designed to carry data between the client and server without any user interaction or visual presence. The browser does not render them, screen readers do not announce them, and they are entirely excluded from the accessibility tree. Because aria-* attributes exist solely to convey information to assistive technologies, adding them to an element that assistive technologies cannot perceive is contradictory and meaningless.
The HTML specification explicitly prohibits aria-* attributes on input elements with type="hidden". This restriction exists because WAI-ARIA attributes — such as aria-label, aria-invalid, aria-describedby, aria-required, and all others in the aria-* family — are meant to enhance the accessible representation of interactive or visible elements. A hidden input has no such representation, so these attributes have nowhere to apply.
This issue commonly arises when:
- JavaScript frameworks or templating engines apply aria-* attributes indiscriminately to all form inputs, regardless of type.
- A developer changes an input’s type from "text" to "hidden" but forgets to remove the accessibility attributes that were relevant for the visible version.
- Form libraries or validation plugins automatically inject attributes like aria-invalid onto every input in a form.
To fix the issue, simply remove all aria-* attributes from any input element that has type="hidden". If the aria-* attribute was meaningful on a previously visible input, no replacement is needed — the hidden input doesn’t participate in the user experience at all.
Examples
Incorrect: hidden input with aria-invalid
<form action="/submit" method="post">
<input type="hidden" name="referer" value="https://example.com" aria-invalid="false">
<button type="submit">Submit</button>
</form>
Correct: hidden input without aria-* attributes
<form action="/submit" method="post">
<input type="hidden" name="referer" value="https://example.com">
<button type="submit">Submit</button>
</form>
Incorrect: hidden input with multiple aria-* attributes
<form action="/save" method="post">
<input
type="hidden"
name="session_token"
value="abc123"
aria-label="Session token"
aria-required="true"
aria-describedby="token-help">
<button type="submit">Save</button>
</form>
Correct: all aria-* attributes removed
<form action="/save" method="post">
<input type="hidden" name="session_token" value="abc123">
<button type="submit">Save</button>
</form>
Correct: aria-* attributes on a visible input (where they belong)
If the input is meant to be visible and accessible, use an appropriate type value instead of "hidden":
<form action="/login" method="post">
<label for="username">Username</label>
<input
type="text"
id="username"
name="username"
aria-required="true"
aria-invalid="false"
aria-describedby="username-help">
<p id="username-help">Enter your registered email or username.</p>
<button type="submit">Log in</button>
</form>
The aria-labelledby attribute is part of the WAI-ARIA specification and provides an accessible name for an element by referencing the id values of other elements that contain the labeling text. Without the aria- prefix, labelledby is simply an unrecognized attribute that browsers and assistive technologies will ignore. This means your SVG graphic won’t have the accessible label you intended, leaving screen reader users without a meaningful description of the content.
This issue is especially important for <svg> elements because SVG graphics are often used for icons, charts, and illustrations that need descriptive labels for accessibility. Using the incorrect attribute name means the graphic is effectively unlabeled for users who rely on assistive technology.
How to Fix It
Replace labelledby with aria-labelledby on your <svg> element. The attribute’s value should be a space-separated list of one or more id values that reference elements containing the label text.
If you want to label an SVG using text that’s already visible on the page, aria-labelledby is the ideal approach. You can also reference a <title> element inside the SVG itself.
Examples
❌ Incorrect: Using labelledby (invalid attribute)
<h2 id="chart-title">Monthly Sales</h2>
<svg labelledby="chart-title" role="img" viewBox="0 0 200 100">
<!-- chart content -->
</svg>
✅ Correct: Using aria-labelledby to reference an external heading
<h2 id="chart-title">Monthly Sales</h2>
<svg aria-labelledby="chart-title" role="img" viewBox="0 0 200 100">
<!-- chart content -->
</svg>
✅ Correct: Using aria-labelledby to reference the SVG’s own <title>
<svg aria-labelledby="icon-title" role="img" viewBox="0 0 24 24">
<title id="icon-title">Search</title>
<path d="M15.5 14h-.79l-.28-.27A6.47 6.47 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5z"/>
</svg>
✅ Correct: Referencing multiple label sources
You can combine multiple id values to build a composite accessible name, separated by spaces:
<h2 id="section-title">Revenue</h2>
<p id="section-desc">Q1 2024 revenue by region</p>
<svg aria-labelledby="section-title section-desc" role="img" viewBox="0 0 400 200">
<!-- chart content -->
</svg>
In this case, a screen reader would announce something like “Revenue Q1 2024 revenue by region” as the accessible name for the SVG.
Tips
- When using aria-labelledby on <svg>, also add role="img" to ensure consistent behavior across screen readers.
- If the SVG is purely decorative, use aria-hidden="true" instead of labeling it.
- The aria-labelledby attribute overrides other labeling mechanisms like aria-label or the <title> element, so use it when you want a specific label to take precedence.
The aria-rowspan attribute is used in ARIA-based grid and table structures to indicate how many rows a cell spans. It serves a similar purpose to the rowspan attribute on native HTML <td> and <th> elements, but is designed for custom widgets built with ARIA roles like gridcell, rowheader, and columnheader within grid or treegrid structures.
According to the WAI-ARIA specification, the value of aria-rowspan must be a positive integer — a whole number greater than zero. A value of "0" is invalid because it implies the cell spans no rows, which is semantically meaningless. Note that this differs from native HTML’s rowspan attribute, where "0" has a special meaning (span all remaining rows in the row group). The ARIA attribute does not support this behavior.
This matters primarily for accessibility. Screen readers and other assistive technologies rely on aria-rowspan to convey the structure of custom grids to users. An invalid value of "0" can confuse assistive technology, potentially causing it to misrepresent the grid layout or skip the cell entirely. Ensuring valid values helps users who depend on these tools navigate your content correctly.
To fix this issue, determine how many rows the cell actually spans and set aria-rowspan to that number. If the cell occupies a single row, use "1". If it spans multiple rows, use the appropriate count. If you don’t need row spanning at all, you can simply remove the aria-rowspan attribute entirely, since the default behavior is to span one row.
Examples
Incorrect: aria-rowspan set to zero
<div role="grid">
<div role="row">
<div role="gridcell" aria-rowspan="0">Name</div>
<div role="gridcell">Value</div>
</div>
</div>
The value "0" is not a positive integer, so the validator reports an error.
Correct: aria-rowspan set to a positive integer
If the cell spans a single row, use "1" (or remove the attribute, since one row is the default):
<div role="grid">
<div role="row">
<div role="gridcell" aria-rowspan="1">Name</div>
<div role="gridcell">Value</div>
</div>
</div>
Correct: aria-rowspan for a cell spanning multiple rows
If the cell genuinely spans two rows, set the value accordingly:
<div role="grid">
<div role="row">
<div role="gridcell" aria-rowspan="2">Category</div>
<div role="gridcell">Item A</div>
</div>
<div role="row">
<div role="gridcell">Item B</div>
</div>
</div>
Correct: removing the attribute when no spanning is needed
If the cell doesn’t span multiple rows, the simplest fix is to remove aria-rowspan altogether:
<div role="grid">
<div role="row">
<div role="gridcell">Name</div>
<div role="gridcell">Value</div>
</div>
</div>
When to use aria-rowspan vs. native HTML
If you’re building a data table, prefer native HTML <table>, <tr>, <td>, and <th> elements with the standard rowspan attribute. Native table semantics are automatically understood by browsers and assistive technologies without any ARIA attributes. Reserve aria-rowspan for custom interactive widgets (like spreadsheet-style grids or tree grids) where native table elements aren’t appropriate. The aria-rowspan value should always match the actual visual and structural layout of your grid to avoid confusing assistive technology users.
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 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".
The aria-expanded attribute communicates to assistive technologies whether a related grouping element (such as a dropdown menu, accordion panel, or collapsible section) is currently expanded or collapsed. It accepts only three valid values:
- "true" — the controlled element is expanded and visible.
- "false" — the controlled element is collapsed and hidden.
- "undefined" — the element has no expandable relationship (this is also the implicit default when the attribute is omitted entirely).
This validation error typically occurs when the attribute is accidentally set to a non-boolean value. A common mistake is writing aria-expanded="aria-expanded", which mimics the old HTML4 pattern for boolean attributes like checked="checked". However, aria-expanded is not a standard HTML boolean attribute — it is an ARIA state attribute that requires an explicit string value of "true" or "false".
Setting an invalid value means assistive technologies like screen readers cannot correctly interpret the state of the control. A screen reader user may not know whether a menu is open or closed, leading to a confusing and inaccessible experience. Browsers may also handle the invalid value unpredictably, potentially treating it as truthy or ignoring it altogether.
How to fix it
- Identify the element with the invalid aria-expanded value.
- Replace the value with "true" if the associated content is currently expanded, or "false" if it is collapsed.
- If the button has no expand/collapse relationship at all, remove the aria-expanded attribute entirely.
- Ensure that JavaScript toggling logic updates the attribute to "true" or "false" — never to any other string.
Examples
❌ Invalid: attribute set to a non-boolean string
<button aria-expanded="aria-expanded" aria-controls="menu">
Toggle Menu
</button>
<ul id="menu">
<li>Option 1</li>
<li>Option 2</li>
</ul>
The value "aria-expanded" is not a recognized value and triggers the validation error.
✅ Fixed: attribute set to "false" (collapsed state)
<button aria-expanded="false" aria-controls="menu">
Toggle Menu
</button>
<ul id="menu" hidden>
<li>Option 1</li>
<li>Option 2</li>
</ul>
✅ Fixed: attribute set to "true" (expanded state)
<button aria-expanded="true" aria-controls="menu">
Toggle Menu
</button>
<ul id="menu">
<li>Option 1</li>
<li>Option 2</li>
</ul>
❌ Invalid: other common incorrect values
<!-- Using "yes" instead of "true" -->
<button aria-expanded="yes">Details</button>
<!-- Using "1" instead of "true" -->
<button aria-expanded="1">Details</button>
<!-- Empty value -->
<button aria-expanded="">Details</button>
All of these are invalid. The only accepted values are "true", "false", and "undefined".
✅ Toggling with JavaScript
When toggling aria-expanded dynamically, make sure the value is always set to the correct string:
<button aria-expanded="false" aria-controls="panel" onclick="togglePanel(this)">
Show details
</button>
<div id="panel" hidden>
<p>Additional details here.</p>
</div>
<script>
function togglePanel(button) {
const expanded = button.getAttribute("aria-expanded") === "true";
button.setAttribute("aria-expanded", String(!expanded));
const panel = document.getElementById(button.getAttribute("aria-controls"));
panel.hidden = expanded;
}
</script>
This ensures the attribute always toggles between "true" and "false", keeping the markup valid and the experience accessible for all users.
The role attribute value combobox is not valid on an input element according to the W3C HTML standard.
The role="combobox" is valid only when applied to an element that acts as a container for the combo box widget, usually a div or similar element, and not directly to a native HTML input. Native input elements of type "text" or "search" already have implicit roles and accessibility semantics. To create an accessible combobox, wrap the input inside a container element with role="combobox" and use appropriate ARIA attributes.
Incorrect Implementation:
<input type="text" role="combobox" aria-autocomplete="list">
Correct Implementation:
<div role="combobox" aria-haspopup="listbox" aria-owns="suggestions" aria-expanded="false">
<input type="text" aria-autocomplete="list" aria-controls="suggestions">
</div>
<ul id="suggestions" role="listbox">
<li role="option" id="option1">Option 1</li>
<li role="option" id="option2">Option 2</li>
</ul>
Explanation of attributes:
- role="combobox": Applied to the container (<div>) to define the accessible widget.
- aria-haspopup="listbox": Indicates the presence of a list of suggestions.
- aria-owns / aria-controls: Connects the input and suggestion list.
- aria-expanded: Denotes whether the suggestion list is visible.
- aria-autocomplete: Defines the autocomplete behavior.
This markup ensures better accessibility and passes W3C validation.
The dialog element does not require or permit a role="dialog" attribute according to HTML standards.
The <dialog> element has an implicit ARIA role of dialog, so adding role="dialog" is redundant and not valid per the specification. Instead, simply use the <dialog> element without an explicit role attribute.
Details:
According to the WHATWG HTML standard and ARIA specification, native <dialog> elements automatically have the correct role. Adding role="dialog" can cause HTML validation errors, as the validator interprets this as a misuse or redundancy.
Correct usage:
<dialog>
<p>This is a dialog box.</p>
<button>Close</button>
</dialog>
Incorrect usage (causes validation error):
<dialog role="dialog">
<p>This is a dialog box.</p>
<button>Close</button>
</dialog>
Removing the role="dialog" attribute resolves the W3C validation issue while maintaining accessibility.
The aria-activedescendant attribute tells assistive technologies which child element within a composite widget — such as a combobox, listbox, or autocomplete dropdown — is currently “active” or focused. Instead of moving actual DOM focus to each option, the parent element (like an input) retains focus while aria-activedescendant points to the visually highlighted option by referencing its id. This allows screen readers to announce the active option without disrupting keyboard interaction on the input.
When aria-activedescendant is set to an empty string (""), it creates an invalid state. The HTML and ARIA specifications require that any ID reference attribute either contains a valid, non-empty ID token or is omitted altogether. An empty string is not a valid ID, so the W3C validator flags this as an error: Bad value “” for attribute “aria-activedescendant” on element “input”: An ID must not be the empty string.
This problem commonly occurs in JavaScript-driven widgets where aria-activedescendant is cleared by setting it to "" when no option is highlighted — for example, when a dropdown closes or the user clears their selection. While the developer’s intent is correct (indicating that nothing is active), the implementation is wrong.
Why this matters
- Accessibility: Screen readers may behave unpredictably when encountering an empty ID reference. Some may silently ignore it, while others may announce errors or fail to convey widget state correctly.
- Standards compliance: The ARIA specification explicitly requires ID reference values to be non-empty strings that match an existing element’s id.
- Browser consistency: Browsers handle invalid ARIA attributes inconsistently, which can lead to different experiences across platforms and assistive technologies.
How to fix it
- Remove the attribute when no descendant is active. Use removeAttribute('aria-activedescendant') in JavaScript instead of setting it to an empty string.
- Set a valid ID when a descendant becomes active, pointing to the id of the currently highlighted or selected option.
- Never render the attribute in HTML with an empty value. If your framework or templating engine conditionally renders attributes, ensure it omits the attribute entirely rather than outputting aria-activedescendant="".
Examples
Incorrect: empty string value
This triggers the W3C validation error because the attribute value is an empty string.
<input type="text" role="combobox" aria-activedescendant="" />
Correct: attribute omitted when no option is active
When nothing is active, simply leave the attribute off.
<input type="text" role="combobox" aria-expanded="false" />
Correct: valid ID reference when an option is active
When a user highlights an option, set aria-activedescendant to that option’s id.
<div role="combobox">
<input
type="text"
role="combobox"
aria-expanded="true"
aria-controls="suggestions"
aria-activedescendant="option2" />
<ul id="suggestions" role="listbox">
<li id="option1" role="option">Apple</li>
<li id="option2" role="option" aria-selected="true">Banana</li>
<li id="option3" role="option">Cherry</li>
</ul>
</div>
Correct: managing the attribute dynamically with JavaScript
The key fix in JavaScript is using removeAttribute instead of setting the value to an empty string.
<div role="combobox">
<input
id="search"
type="text"
role="combobox"
aria-expanded="true"
aria-controls="results" />
<ul id="results" role="listbox">
<li id="result1" role="option">First result</li>
<li id="result2" role="option">Second result</li>
</ul>
</div>
<script>
const input = document.getElementById('search');
function setActiveOption(optionId) {
if (optionId) {
input.setAttribute('aria-activedescendant', optionId);
} else {
// Remove the attribute instead of setting it to ""
input.removeAttribute('aria-activedescendant');
}
}
</script>
In summary, always ensure aria-activedescendant either points to a real, non-empty id or is removed from the element. Never set it to an empty string.
The validator error occurs when an element such as an a, button, or custom widget includes aria-controls="" (empty) or whitespace-only. The aria-controls attribute takes one or more space-separated id values (IDREFS). Each referenced id must exist exactly once in the same document. Leaving it empty violates the ARIA and HTML requirements and provides no usable relationship for assistive technologies.
Why this matters:
- Accessibility: Screen readers rely on aria-controls to announce relationships between controls and controlled regions (e.g., a toggle and its panel). An empty value misleads AT or adds noise.
- Standards compliance: HTML and ARIA require at least one non-whitespace id. Empty values cause validation failures.
- Robustness: Incorrect references can confuse scripts and future maintainers, and break behavior when IDs change.
How to fix it:
- Only add aria-controls when the element truly controls another region (show/hide, sort, update).
- Ensure the controlled element has a unique id.
- Set aria-controls to that id (or multiple space-separated IDs).
- Keep the reference in sync if IDs change.
- If nothing is controlled, remove aria-controls entirely.
Examples
Invalid: empty aria-controls (triggers the error)
<a href="#" aria-controls="">Toggle details</a>
Valid: control a single region
<div id="details-panel" hidden>
Some details...
</div>
<a href="#details-panel" aria-controls="details-panel">Toggle details</a>
Valid: control multiple regions (space-separated IDs)
<section id="filters" hidden>...</section>
<section id="results" hidden>...</section>
<button type="button" aria-controls="filters results">Show filters and results</button>
Valid: remove when not needed
<a href="#">Toggle details</a>
Minimal complete document with proper usage
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>aria-controls Example</title>
</head>
<body>
<button type="button" aria-controls="info" aria-expanded="false">Toggle info</button>
<div id="info" hidden>
Extra information.
</div>
<script>
const btn = document.querySelector('button');
const panel = document.getElementById(btn.getAttribute('aria-controls'));
btn.addEventListener('click', () => {
const expanded = btn.getAttribute('aria-expanded') === 'true';
btn.setAttribute('aria-expanded', String(!expanded));
panel.hidden = expanded;
});
</script>
</body>
</html>
Tips:
- Use aria-controls for functional relationships (control affects content), not just visual proximity.
- Combine with aria-expanded when toggling visibility to convey state.
- Verify that every id in aria-controls exists and is unique; avoid dynamic mismatches created by templating or component reuse.
Set a non-empty list of valid ID references in the aria-labelledby attribute on the <a> element, or remove the attribute and provide an accessible name another way.
The aria-labelledby attribute takes an IDREFS value: a space-separated list of one or more element IDs, each of which must be non-empty, exist in the document, and be unique. An empty value ("") violates the ARIA/HTML constraints and triggers the validator error. On an <a> element, aria-labelledby supplies the accessible name for the link by concatenating the text from the referenced elements. If you don’t have IDs to reference, use visible link text or aria-label. Avoid leaving aria-labelledby empty (common with templating when a variable is blank); either omit the attribute entirely or populate it with valid IDs. Remember that aria-labelledby overrides other naming methods, so an empty or broken reference can result in a link with no accessible name.
HTML examples
-
Invalid (what triggers the error)
<a href="/report" aria-labelledby=""></a> -
Fixed by referencing an existing element with a valid id
<a href="/report" aria-labelledby="report-link-text"> <svg aria-hidden="true" viewBox="0 0 16 16"></svg> </a> <span id="report-link-text">View report</span> -
Fixed with multiple IDs (space-separated)
<a href="/apples" aria-labelledby="prefix apples-text"> <svg aria-hidden="true" viewBox="0 0 16 16"></svg> </a> <span id="prefix">Learn more: </span> <span id="apples-text">Apples</span> -
Fixed by using visible text content (no ARIA needed)
<a href="/report">View report</a> -
Fixed by using aria-label for an icon-only link (when no separate label element exists)
<a href="/search" aria-label="Search"> <svg aria-hidden="true" viewBox="0 0 16 16"></svg> </a>
Empty aria-labelledby on the <svg> is invalid because it must reference one or more existing IDs with non-empty, non-whitespace text.
The aria-labelledby attribute takes a space-separated list of element IDs (IDREFS). Each ID must exist in the document and point to elements that provide an accessible name. On <svg>, this is commonly a <title> or other visible text element. If you have no label to reference, either remove aria-labelledby or provide a valid referenced element. Alternatively, use the aria-label attribute with a text string. Do not leave aria-labelledby empty, and ensure IDs are unique and match exactly (case-sensitive). Examples: reference a <title> with an id, or use aria-label directly on the <svg>.
HTML Examples
Example showing the issue
<svg role="img" aria-labelledby="">
<use href="#icon-star"></use>
</svg>
Fixed examples
<!-- Option A: Reference a title by ID -->
<svg role="img" aria-labelledby="starTitle">
<title id="starTitle">Favorite</title>
<use href="#icon-star"></use>
</svg>
<!-- Option B: Use aria-label instead (no referenced IDs needed) -->
<svg role="img" aria-label="Favorite">
<use href="#icon-star"></use>
</svg>
<!-- Option C: Decorative icon (no name) -->
<svg aria-hidden="true" focusable="false">
<use href="#icon-star"></use>
</svg>
The WAI-ARIA specification defines a specific set of role values that assistive technologies like screen readers understand. These include roles such as button, checkbox, alert, dialog, img, navigation, banner, and many others. The value "icon" is not among them. When a browser or assistive technology encounters an unrecognized role, it cannot determine the element’s purpose, which defeats the goal of using ARIA in the first place.
This is primarily an accessibility problem. Screen readers rely on valid ARIA roles to communicate the nature of elements to users. An invalid role like "icon" is either ignored or causes unpredictable behavior, leaving users of assistive technologies without the context they need. It’s also a standards compliance issue — the W3C validator flags this because the HTML specification requires role values to match roles defined in the ARIA specification.
The fix depends on the purpose of the element:
- Decorative icons (that don’t convey information): Remove the role attribute entirely, or use aria-hidden="true" to explicitly hide the element from the accessibility tree.
- Meaningful icons (that convey information visually): Use role="img" along with an aria-label to provide a text alternative.
- Icons inside interactive elements: Hide the icon with aria-hidden="true" and ensure the parent interactive element has an accessible name through visible text or an aria-label.
Examples
❌ Invalid: Using the non-existent "icon" role
<span class="icon" role="icon"></span>
This triggers the validation error because "icon" is not a valid ARIA role.
✅ Fixed: Decorative icon with no role
If the icon is purely decorative and doesn’t convey any meaning (e.g., it’s next to text that already describes the action), simply remove the role attribute. Adding aria-hidden="true" ensures screen readers skip over it completely.
<span class="icon" aria-hidden="true"></span>
✅ Fixed: Meaningful icon using role="img"
If the icon conveys meaningful information that isn’t available through surrounding text, use role="img" and provide a descriptive aria-label:
<span class="icon-warning" role="img" aria-label="Warning"></span>
This tells assistive technologies that the element represents an image and gives it an accessible name of “Warning.”
✅ Fixed: Icon inside a button
When an icon is placed inside an interactive element like a button, hide the icon from the accessibility tree and let the button’s text or label provide the meaning:
<button>
<span class="icon-save" aria-hidden="true"></span>
Save
</button>
If the button has no visible text (an icon-only button), provide an aria-label on the button itself:
<button aria-label="Save">
<span class="icon-save" aria-hidden="true"></span>
</button>
✅ Fixed: Icon using an <img> element instead
If you’re using an actual image file for the icon, consider using a semantic <img> element, which has a built-in img role:
<img src="icon-alert.svg" alt="Alert" class="icon">
For decorative image icons, use an empty alt attribute:
<img src="icon-decorative.svg" alt="" class="icon">
This error occurs when you use role="presentational" on an HTML element. While the intent is clear — you want to strip the element’s implicit ARIA semantics — the value presentational does not exist in the WAI-ARIA specification. It’s a common typo or misconception. The two valid roles for this purpose are presentation and none, which are synonyms of each other.
When you apply role="presentation" or role="none" to an element, you’re telling assistive technologies (like screen readers) to ignore the element’s implicit semantic meaning. For example, a <table> used purely for layout purposes has no tabular data semantics, so role="presentation" removes the table-related semantics from the accessibility tree. However, the content inside the element remains accessible — only the container’s semantics (and in some cases, its required associated descendants) are removed.
This matters for several reasons:
- Standards compliance: Using an invalid role value means the attribute is essentially ignored by browsers and assistive technologies, so the element’s semantics are not removed as intended.
- Accessibility: If a screen reader encounters an unknown role, it may fall back to the element’s default semantics, leading to a confusing experience. For instance, a layout table without a valid presentation role may still be announced as a data table.
- Browser consistency: Invalid ARIA values can lead to unpredictable behavior across different browsers and assistive technologies.
To fix this, simply replace presentational with presentation or none.
Examples
❌ Incorrect: using the invalid presentational role
<table role="presentational">
<tr>
<td>Column 1</td>
<td>Column 2</td>
</tr>
</table>
✅ Correct: using presentation
<table role="presentation">
<tr>
<td>Column 1</td>
<td>Column 2</td>
</tr>
</table>
✅ Correct: using none (synonym for presentation)
<table role="none">
<tr>
<td>Column 1</td>
<td>Column 2</td>
</tr>
</table>
Other elements where this applies
The presentation / none role can be used on various elements, not just tables. Here’s another common example with an image used purely for decoration:
<!-- ❌ Incorrect -->
<img src="divider.png" alt="" role="presentational">
<!-- ✅ Correct -->
<img src="divider.png" alt="" role="presentation">
When to use presentation vs. none
Both presentation and none are functionally identical. The none role was introduced later as a clearer name, since presentation can be confused with visual presentation. For maximum browser support, some developers use both:
<table role="none presentation">
<tr>
<td>Layout content</td>
</tr>
</table>
This fallback pattern ensures that older assistive technologies that don’t recognize none will still pick up presentation. However, in most modern environments, either value alone is sufficient.
The search ARIA role is a landmark role, which means it identifies a large, navigable section of a page — specifically, the region that contains the search functionality. Landmark roles help assistive technologies (like screen readers) quickly identify and jump to major sections of a document. Because landmarks describe sections of a page, they belong on container elements that encompass all the parts of the search interface (the label, the input field, the submit button, etc.), not on a single <input> element.
When you place role="search" on an <input>, the validator rejects it because the search role doesn’t match the semantics of an input control. An <input> represents a single interactive widget, not a page region. The valid way to indicate that an input field is for search queries is to use <input type="search">, which gives browsers and assistive technologies the correct semantic meaning for that specific control.
Meanwhile, if you want to mark an entire search form as a search landmark, apply role="search" to the <form> element that wraps the search controls. In modern HTML, you can also use the <search> element, which has the implicit search landmark role without needing any ARIA attribute.
How to fix it
- Remove role="search" from the <input> element.
- Change the input’s type to "search" — this tells browsers and assistive technologies that the field is for search queries.
- Apply role="search" to the wrapping <form>, or use the HTML <search> element as the container.
Examples
❌ Incorrect: role="search" on an <input>
<form>
<label for="query">Search</label>
<input role="search" id="query" name="q">
<button type="submit">Go</button>
</form>
This triggers the validation error because search is not a valid role for <input>.
✅ Correct: type="search" on the input, role="search" on the form
<form role="search">
<label for="query">Search this site</label>
<input type="search" id="query" name="q">
<button type="submit">Go</button>
</form>
Here, role="search" is correctly placed on the <form> element, creating a search landmark. The <input type="search"> conveys the correct semantics for the input field itself.
✅ Correct: Using the <search> element (modern HTML)
<search>
<form>
<label for="query">Search this site</label>
<input type="search" id="query" name="q">
<button type="submit">Go</button>
</form>
</search>
The <search> element has the implicit ARIA role of search, so no explicit role attribute is needed on either the container or the form. This is the most semantic approach in browsers that support it.
✅ Correct: Standalone search input without a landmark
If you simply need a search-styled input without marking up a full landmark region, just use type="search":
<label for="filter">Filter results</label>
<input type="search" id="filter" name="filter">
This gives the input the correct semantics and allows browsers to provide search-specific UI features (such as a clear button) without requiring a landmark role.
The value section does not exist in the WAI-ARIA specification. ARIA defines a specific set of role values, and section is not among them. This is likely a confusion between the HTML element name <section> and the ARIA role region, which is the role that the <section> element implicitly maps to. Because section is not a recognized role, the validator rejects it as an invalid value.
This matters for several reasons. First, assistive technologies like screen readers rely on ARIA roles to communicate the purpose of elements to users. An unrecognized role value may be ignored entirely or cause unexpected behavior, degrading the experience for users who depend on these tools. Second, the <section> element already carries native semantics equivalent to role="region" (when it has an accessible name), so adding a redundant or incorrect role provides no benefit and introduces potential problems.
According to the ARIA in HTML specification, you should generally avoid setting a role on elements that already have appropriate native semantics. The <section> element’s implicit role is region, so explicitly adding role="region" is redundant in most cases. The simplest and best fix is to remove the role attribute altogether and let the native HTML semantics do their job.
If you do need to override an element’s role for a specific design pattern (for example, turning a <section> into a navigation landmark), use a valid ARIA role from the WAI-ARIA specification.
Examples
Incorrect: using the invalid section role
<section role="section">
<h2>About Us</h2>
<p>Learn more about our team.</p>
</section>
This triggers the validation error because section is not a valid ARIA role value.
Correct: remove the role attribute
<section>
<h2>About Us</h2>
<p>Learn more about our team.</p>
</section>
The <section> element already provides the correct semantics. No role attribute is needed.
Correct: use a valid ARIA role if needed
If you have a specific reason to assign a role, use a valid one. For example, if a <section> is being used as a navigation landmark:
<section role="navigation" aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</section>
In practice, you would typically use a <nav> element instead, which has the navigation role natively. This example simply illustrates that if you do apply a role, it must be a valid ARIA role value.
Correct: explicit region role with an accessible name
If you want to explicitly mark a section as a named region landmark, you can use role="region" along with an accessible name. However, this is redundant when using <section> with an aria-label or aria-labelledby, since the browser already maps it to region:
<!-- Preferred: native semantics handle the role -->
<section aria-labelledby="features-heading">
<h2 id="features-heading">Features</h2>
<p>Explore our product features.</p>
</section>
<!-- Also valid but redundant -->
<section role="region" aria-labelledby="features-heading">
<h2 id="features-heading">Features</h2>
<p>Explore our product features.</p>
</section>
Both are valid HTML, but the first approach is cleaner and follows the principle of relying on native semantics whenever possible.
The role attribute exposes an element’s purpose to assistive technologies. ARIA defines a fixed set of role values; sidebar is not among them, so validators report a bad value. Sidebars typically contain related or ancillary content, which maps to the complementary landmark role. In HTML, the <aside> element already represents this concept and implicitly maps to the complementary role.
Leaving an invalid role harms accessibility because screen readers may ignore the landmark or misreport it, and automated tools can’t build a reliable landmarks map. Standards compliance also matters for consistent behavior across browsers and assistive tech.
To fix it:
- Replace role="sidebar" with role="complementary" on a generic container; add an accessible name with aria-labelledby or aria-label when multiple complementary regions exist.
- Prefer <aside> for semantic HTML. It implicitly has the complementary role; add a label when there is more than one <aside>.
- Do not add role="complementary" to <aside> unless you need to override something; duplicate roles are unnecessary.
- If the area is site-wide navigation, use <nav> or role="navigation" instead; choose the role that best matches the intent.
Examples
Invalid: non-existent ARIA role
<div role="sidebar">
<!-- Related links and promos -->
</div>
Fixed: use the complementary role on a generic container
<div role="complementary" aria-labelledby="sidebar-title">
<h2 id="sidebar-title">Related</h2>
<ul>
<li><a href="/guide-a">Guide A</a></li>
<li><a href="/guide-b">Guide B</a></li>
</ul>
</div>
Fixed: use semantic HTML with aside (implicit complementary)
<aside aria-labelledby="sidebar-title">
<h2 id="sidebar-title">Related</h2>
<ul>
<li><a href="/guide-a">Guide A</a></li>
<li><a href="/guide-b">Guide B</a></li>
</ul>
</aside>
Multiple sidebars: ensure unique, descriptive labels
<aside aria-labelledby="filters-title">
<h2 id="filters-title">Filter results</h2>
<!-- filter controls -->
</aside>
<aside aria-labelledby="related-title">
<h2 id="related-title">Related articles</h2>
<!-- related links -->
</aside>
When it’s actually navigation: use the navigation landmark
<nav aria-label="Section navigation">
<ul>
<li><a href="#intro">Intro</a></li>
<li><a href="#examples">Examples</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
Tips:
- Use <aside> for tangential content; it’s the simplest, standards-based approach.
- Provide an accessible name when more than one complementary region is present.
- Avoid inventing ARIA roles; stick to defined values like banner, main, navigation, complementary, contentinfo, and others.
ARIA defines a fixed set of role values that user agents and assistive technologies understand. sidebar is not in that set, so role="sidebar" fails conformance checking and gives unreliable signals to screen readers. Using a valid role or the correct HTML element improves accessibility, ensures consistent behavior across browsers and AT, and keeps your markup standards‑compliant.
Sidebars typically contain tangential or ancillary content (e.g., related links, promos, author info). The ARIA role that matches that meaning is complementary. In HTML, the semantic element for the same concept is aside, which by default maps to the complementary landmark in accessibility APIs. Prefer native semantics first: use <aside> when possible. Only add role="complementary" when you can’t change the element type or when you need an explicit landmark for non-semantic containers.
How to fix:
- If the element is a sidebar: change <div role="sidebar"> to <aside> (preferred), or to <div role="complementary">.
- Ensure each page has at most one primary main region and that complementary regions are not essential to understanding the main content.
- Provide an accessible name for the complementary region when multiple exist, using aria-label or aria-labelledby, to help users navigate landmarks.
Examples
Triggers the validator error
<div role="sidebar">
<!-- Sidebar content -->
</div>
Fixed: use the semantic element (preferred)
<aside aria-label="Related articles">
<!-- Sidebar content -->
</aside>
Fixed: keep the container, apply a valid role
<div role="complementary" aria-label="Related articles">
<!-- Sidebar content -->
</div>
Full document example with two sidebars (each labeled)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Sidebar Landmarks Example</title>
</head>
<body>
<header>
<h1>News Today</h1>
</header>
<main id="main">
<article>
<h2>Main Story</h2>
<p>...</p>
</article>
</main>
<aside aria-label="Trending topics">
<ul>
<li>Science</li>
<li>Politics</li>
<li>Sports</li>
</ul>
</aside>
<div role="complementary" aria-labelledby="sponsor-title">
<h2 id="sponsor-title">Sponsored</h2>
<p>Ad content</p>
</div>
<footer>
<p>© 2026</p>
</footer>
</body>
</html>
Notes:
- Do not invent ARIA roles (e.g., sidebar, hero, footer-nav). Use defined roles like complementary, navigation, banner, contentinfo, and main.
- Prefer native HTML elements (aside, nav, header, footer, main) over generic containers with roles.
- Label multiple complementary landmarks to make them distinguishable in screen reader landmark lists.
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.
Ready to validate your sites?
Start your free trial today.