HTML Guides for role
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.
The HTML <figure> element represents self-contained content — such as an image, diagram, code snippet, or quotation — optionally accompanied by a caption provided by a <figcaption> element. When a <figcaption> is present, the browser and assistive technologies already understand the relationship between the figure and its caption. This built-in semantic relationship is part of the ARIA in HTML specification, which governs how native HTML elements map to accessibility roles.
The role attribute is typically used to assign or override the ARIA role of an element for assistive technologies like screen readers. However, the ARIA in HTML specification explicitly restricts certain role assignments when an element’s native semantics are already well-defined by its content. A <figure> with a <figcaption> is one such case — the presence of the caption establishes a clear semantic structure that the role attribute would interfere with.
This restriction exists for several important reasons:
- Accessibility conflicts: Adding role="figure" is redundant since the element already has that implicit role. Adding a different role (like role="img" or role="group") could confuse assistive technologies by contradicting the semantic meaning established by the <figcaption>. Screen readers may ignore the caption or announce the element incorrectly.
- Standards compliance: The ARIA in HTML specification states that when a <figure> has a <figcaption> descendant, no role attribute is allowed. The W3C validator enforces this rule.
- Maintainability: Relying on native HTML semantics rather than ARIA overrides keeps your markup simpler and less error-prone. The first rule of ARIA is: “If you can use a native HTML element with the semantics and behavior you require already built in, do so.”
To fix this issue, remove the role attribute from the <figure> element. The <figcaption> provides all the semantic context needed.
Examples
Incorrect: role attribute on <figure> with <figcaption>
Adding role="figure" is redundant and triggers the validation error:
<figure role="figure">
<img src="chart.png" alt="Sales data for Q3 2024">
<figcaption>Figure 1: Quarterly sales by region.</figcaption>
</figure>
Using a different role like role="img" also triggers the error and can cause accessibility problems:
<figure role="img">
<img src="chart.png" alt="Sales data for Q3 2024">
<figcaption>Figure 1: Quarterly sales by region.</figcaption>
</figure>
Correct: <figure> with <figcaption> and no role
Simply remove the role attribute. The <figure> and <figcaption> elements handle semantics on their own:
<figure>
<img src="chart.png" alt="Sales data for Q3 2024">
<figcaption>Figure 1: Quarterly sales by region.</figcaption>
</figure>
Correct: <figure> without <figcaption> can use a role
If you have a <figure> without a <figcaption>, the restriction does not apply. In this case, you may use a role attribute if needed:
<figure role="img" aria-label="Sales data for Q3 2024">
<img src="chart.png" alt="">
</figure>
Correct: <figure> with <figcaption> containing other media
The fix applies regardless of the type of content inside the <figure>:
<figure>
<pre><code>const greeting = "Hello, world!";</code></pre>
<figcaption>A simple variable assignment in JavaScript.</figcaption>
</figure>
<figure>
<blockquote>
<p>The best way to predict the future is to invent it.</p>
</blockquote>
<figcaption>Alan Kay</figcaption>
</figure>
In every case, the <figcaption> provides the accessible name and descriptive context for the <figure>, making any role attribute unnecessary and non-conformant.
The ARIA specification defines a strict ownership hierarchy for table-related roles. A role="cell" element must be “owned by” an element with role="row", meaning it must either be a direct child of that element or be associated with it via the aria-owns attribute. This mirrors how native HTML tables work: a <td> element must live inside a <tr> element. When you use ARIA roles to build custom table structures from non-table elements like <div> or <span>, you are responsible for maintaining this same hierarchy manually.
The expected nesting order for an ARIA table is:
- role="table" — the outermost container
- role="rowgroup" (optional) — groups rows together, like <thead>, <tbody>, or <tfoot>
- role="row" — a single row of cells
- role="cell" or role="columnheader" / role="rowheader" — individual cells
When a role="cell" element is placed directly inside a role="table" or any other container that isn’t role="row", screen readers lose the ability to announce row and column positions. Users who rely on table navigation shortcuts (such as moving between cells with arrow keys) will find the table unusable. This is not just a validation concern — it directly impacts whether people can access your content.
This issue commonly arises when developers add intermediate wrapper elements for styling purposes and accidentally break the required parent-child relationship, or when they forget to assign role="row" to a container element.
How to Fix
Ensure every element with role="cell" is a direct child of an element with role="row". If you have wrapper elements between the row and cell for layout or styling, either remove them, move the role assignments, or use aria-owns on the row element to explicitly claim ownership of the cells.
Examples
Incorrect — Cell Without a Row Parent
This triggers the validation error because role="cell" elements are direct children of the role="table" container, with no role="row" in between.
<div role="table">
<div role="cell">Name</div>
<div role="cell">Email</div>
</div>
Incorrect — Intermediate Wrapper Breaking Ownership
Here, a styling wrapper sits between the row and its cells. Since the <div> without a role is not a role="row", the cells are not properly owned.
<div role="table">
<div role="row">
<div class="cell-wrapper">
<div role="cell">Row 1, Cell 1</div>
<div role="cell">Row 1, Cell 2</div>
</div>
</div>
</div>
Correct — Cells Directly Inside Rows
Each role="cell" is a direct child of a role="row" element, forming a valid ARIA table structure.
<div role="table" aria-label="Team members">
<div role="row">
<div role="columnheader">Name</div>
<div role="columnheader">Email</div>
</div>
<div role="row">
<div role="cell">Alice</div>
<div role="cell">alice@example.com</div>
</div>
<div role="row">
<div role="cell">Bob</div>
<div role="cell">bob@example.com</div>
</div>
</div>
Correct — Using Rowgroups
You can optionally group rows with role="rowgroup", similar to <thead> and <tbody>. The cells still must be direct children of their rows.
<div role="table" aria-label="Quarterly results">
<div role="rowgroup">
<div role="row">
<div role="columnheader">Quarter</div>
<div role="columnheader">Revenue</div>
</div>
</div>
<div role="rowgroup">
<div role="row">
<div role="cell">Q1</div>
<div role="cell">$10,000</div>
</div>
<div role="row">
<div role="cell">Q2</div>
<div role="cell">$12,500</div>
</div>
</div>
</div>
Correct — Using Native HTML Instead
If your content is genuinely tabular data, consider using native HTML table elements instead of ARIA roles. Native tables have built-in semantics and require no additional role attributes.
<table>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
<tr>
<td>Alice</td>
<td>alice@example.com</td>
</tr>
</table>
Native HTML tables are always preferable when they suit your use case. The first rule of ARIA is: if you can use a native HTML element that already has the semantics you need, use it instead of adding ARIA roles to a generic element.
The ARIA specification defines a strict ownership hierarchy for table-related roles. A columnheader represents a header cell for a column, analogous to a <th> element in native HTML. For assistive technologies like screen readers to correctly announce column headers and associate them with the data cells beneath them, the columnheader must exist within a row context. The required structure is:
- An element with role="table", role="grid", or role="treegrid" serves as the container.
- Inside it, elements with role="rowgroup" (optional) or role="row" organize the rows.
- Each role="row" element contains one or more elements with role="columnheader", role="rowheader", or role="cell".
When a role="columnheader" element is placed directly inside a role="table" or role="grid" container — or any other element that is not role="row" — the validator raises this error. Without the row wrapper, screen readers cannot navigate the table structure properly. Users who rely on assistive technology may hear disjointed content or miss the column headers entirely, making the data table unusable.
The best practice, whenever feasible, is to use native HTML table elements (<table>, <thead>, <tr>, <th>, <td>). These carry implicit ARIA roles and establish the correct ownership relationships automatically, eliminating this entire category of errors. Only use ARIA table roles when you genuinely cannot use native table markup — for example, when building a custom grid widget with non-table elements for layout reasons.
Examples
Incorrect: columnheader not inside a row
In this example, the columnheader elements are direct children of the table container, with no role="row" wrapper:
<div role="table" aria-label="Employees">
<div role="columnheader">Name</div>
<div role="columnheader">Department</div>
<div role="row">
<div role="cell">Alice</div>
<div role="cell">Engineering</div>
</div>
</div>
Correct: columnheader inside a row
Wrapping the column headers in an element with role="row" fixes the issue:
<div role="table" aria-label="Employees">
<div role="row">
<div role="columnheader">Name</div>
<div role="columnheader">Department</div>
</div>
<div role="row">
<div role="cell">Alice</div>
<div role="cell">Engineering</div>
</div>
</div>
Correct: Using rowgroup for additional structure
You can optionally use role="rowgroup" to separate headers from body rows, similar to <thead> and <tbody>. The columnheader elements must still be inside a row:
<div role="table" aria-label="Employees">
<div role="rowgroup">
<div role="row">
<div role="columnheader">Name</div>
<div role="columnheader">Department</div>
</div>
</div>
<div role="rowgroup">
<div role="row">
<div role="cell">Alice</div>
<div role="cell">Engineering</div>
</div>
</div>
</div>
Best practice: Use native table elements
Native HTML tables have built-in semantics that make ARIA roles unnecessary. A <th> inside a <tr> already behaves as a columnheader inside a row:
<table>
<thead>
<tr>
<th>Name</th>
<th>Department</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td>Engineering</td>
</tr>
</tbody>
</table>
This approach is simpler, more robust across browsers and assistive technologies, and avoids the risk of ARIA misuse. Reserve ARIA table roles for situations where native table markup is not an option.
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.
The HTML specification defines strict rules about which elements can be nested inside others. The <a> element is classified as interactive content, and its content model explicitly forbids other interactive content as descendants. Elements with role="button" — whether a native <button> or any element with the ARIA role — are also interactive. Nesting one inside the other creates an ambiguous situation: should a click activate the link or the button?
This matters for several important reasons:
- Accessibility: Screen readers and assistive technologies rely on a clear, unambiguous element hierarchy. When a button is inside a link, the announced role becomes confusing — users may hear both a link and a button, or the assistive technology may only expose one of them, hiding the other’s functionality.
- Unpredictable behavior: Browsers handle nested interactive elements inconsistently. Some may split the elements apart in the DOM, while others may swallow click events. This leads to broken or unreliable behavior across different browsers.
- Standards compliance: The WHATWG HTML Living Standard and W3C HTML specification both explicitly prohibit this nesting pattern.
To fix this issue, decide what the element should actually do. If it navigates to a URL, use an <a> element. If it performs an action (like submitting a form or triggering JavaScript), use a <button>. If you need both visual styles, use CSS to style one element to look like the other.
Examples
❌ Incorrect: Element with role="button" inside an <a>
<a href="/dashboard">
<span role="button">Go to Dashboard</span>
</a>
The <span> with role="button" is interactive content nested inside the interactive <a> element.
❌ Incorrect: <button> inside an <a>
<a href="/settings">
<button>Open Settings</button>
</a>
A <button> element is inherently interactive and must not appear inside an <a>.
✅ Correct: Use a link styled as a button
If the purpose is navigation, use the <a> element and style it with CSS:
<a href="/dashboard" class="btn">Go to Dashboard</a>
✅ Correct: Use a button that navigates via JavaScript
If you need a button that also navigates, handle navigation in JavaScript:
<button type="button" onclick="location.href='/dashboard'">Go to Dashboard</button>
✅ Correct: Place elements side by side
If you truly need both a link and a button, keep them as siblings rather than nesting one inside the other:
<div class="actions">
<a href="/dashboard">Go to Dashboard</a>
<button type="button">Save Preference</button>
</div>
✅ Correct: Link styled as a button using ARIA (when appropriate)
If the element navigates but you want assistive technologies to announce it as a button, you can apply role="button" directly to the element — just don’t nest it inside an <a>:
<span role="button" tabindex="0" onclick="location.href='/dashboard'">
Go to Dashboard
</span>
Note that using role="button" on a non-interactive element like <span> requires you to also add tabindex="0" for keyboard focusability and handle keydown events for the Enter and Space keys. In most cases, a native <a> or <button> element is the better choice.
An empty alt attribute on an img element is a deliberate signal that the image is purely decorative and carries no meaningful content. According to the WHATWG HTML specification, this maps the element to the “presentation” role in the accessibility tree, effectively hiding it from screen readers and other assistive technologies.
When you add a role attribute to an img with alt="", you’re sending contradictory instructions: the empty alt says “this image is decorative, ignore it,” while the role attribute says “this image has a specific semantic purpose.” Browsers and assistive technologies cannot reliably resolve this conflict, which can lead to confusing or inconsistent behavior for users who rely on screen readers.
This rule exists to enforce clarity in how images are exposed to the accessibility tree. If an image is truly decorative, it should have alt="" and no role. If an image serves a functional or semantic purpose — such as acting as a button, link, or illustration — it should have both a descriptive alt value and, if needed, an appropriate role.
How to fix it
You have two options depending on the image’s purpose:
-
The image is decorative: Remove the role attribute entirely. The empty alt attribute already communicates that the image should be ignored by assistive technologies.
-
The image is meaningful: Provide a descriptive alt value that explains the image’s purpose. If a specific role is genuinely needed, keep it alongside the non-empty alt.
Examples
❌ Incorrect: empty alt with a role attribute
<img src="icon.png" alt="" role="img">
<img src="banner.jpg" alt="" role="presentation">
Even role="presentation" is redundant and invalid here — the empty alt already implies presentational semantics.
✅ Correct: decorative image with no role
<img src="icon.png" alt="">
If the image is decorative, simply remove the role attribute. The empty alt is sufficient.
✅ Correct: meaningful image with a descriptive alt and a role
<img src="warning-icon.png" alt="Warning" role="img">
If the image conveys information, give it a descriptive alt value. The role="img" is typically unnecessary here since img elements already have an implicit role of img when alt is non-empty, but it is at least valid.
✅ Correct: meaningful image used in a specific context
<button>
<img src="search-icon.png" alt="Search">
</button>
Here the image has a descriptive alt and doesn’t need an explicit role because its purpose is conveyed through the alt text and its context within the button.
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 W3C HTML validator enforces rules about which ARIA roles can be applied to specific HTML elements. The <section> element carries implicit semantics — it maps to the ARIA region role when it has an accessible name (e.g., via aria-label or aria-labelledby). While article is indeed a valid ARIA role defined in the WAI-ARIA specification, the HTML specification restricts which roles can override a <section> element’s native semantics. The allowed roles for <section> include alert, alertdialog, application, contentinfo, dialog, document, feed, log, main, marquee, navigation, none, note, presentation, search, status, tabpanel, and region — but not article.
This restriction exists because HTML already provides the <article> element, which carries the implicit article ARIA role natively. Using role="article" on a <section> creates a confusing mismatch: the element’s tag name suggests one semantic meaning while the role attribute declares another. This can confuse assistive technologies like screen readers, which may announce the element inconsistently depending on whether they prioritize the tag name or the explicit role.
The best fix depends on your intent:
- If the content is self-contained and independently meaningful (like a blog post, comment, or news story), replace the <section> with an <article> element. The <article> element already has the implicit article role, so no role attribute is needed.
- If the content is a thematic grouping within a page, keep the <section> element and remove the role attribute. Give it a heading or an aria-label so it functions as a meaningful landmark.
- If you specifically need the article role on a non-semantic element, use a <div> with role="article" instead, since <div> has no implicit role and allows any ARIA role to be applied.
Examples
Incorrect: role="article" on a <section>
This triggers the validation error because article is not a permitted role for <section>.
<section role="article">
<h2>Breaking news</h2>
<p>Details about the event.</p>
</section>
Correct: use <article> for self-contained content
The <article> element has the implicit article role, making the explicit role attribute unnecessary.
<article>
<h2>Breaking news</h2>
<p>Details about the event.</p>
</article>
Correct: use <section> without a conflicting role
If the content is a thematic grouping rather than a standalone piece, keep <section> and drop the role attribute. Adding an accessible name via aria-labelledby makes it a region landmark.
<section aria-labelledby="news-heading">
<h2 id="news-heading">Latest news</h2>
<p>Details about the event.</p>
</section>
Correct: use a <div> when you need an explicit article role
In rare cases where you cannot use the <article> element but need the article role, a <div> accepts any valid ARIA role.
<div role="article">
<h2>Breaking news</h2>
<p>Details about the event.</p>
</div>
Correct: nest <article> inside <section> for grouped articles
If you need both a thematic grouping and individual self-contained items, nest <article> elements inside a <section>.
<section aria-labelledby="stories-heading">
<h2 id="stories-heading">Top stories</h2>
<article>
<h3>First story</h3>
<p>Story content.</p>
</article>
<article>
<h3>Second story</h3>
<p>Story content.</p>
</article>
</section>
As a general rule, prefer native HTML elements over ARIA roles whenever possible. The <article> element communicates the article role more reliably than any ARIA override, and it works consistently across all browsers and assistive technologies without additional attributes.
The ARIA in HTML specification defines which roles are allowed on each HTML element. Heading elements (<h1>–<h6>) have an implicit role of heading, and the set of roles they can be explicitly assigned is limited. The button role is not among them, so applying role="button" directly to a heading element is invalid.
This matters for several reasons. First, headings play a critical role in document structure and accessibility — screen reader users rely on headings to navigate and understand the page hierarchy. Assigning role="button" to a heading overrides its semantic meaning, which confuses assistive technologies. Second, browsers and screen readers may handle conflicting semantics unpredictably, leading to an inconsistent experience for users. Third, it violates the W3C HTML specification, which means your markup won’t pass validation.
Why This Combination Is Problematic
When you apply role="button" to an element, assistive technologies treat it as an interactive button. This completely replaces the element’s native heading semantics. Users who navigate by headings would no longer find that heading in their list, and users who navigate by interactive controls would encounter a “button” that may not behave like one (lacking keyboard support, focus management, etc.).
If you genuinely need something that looks like a heading but acts as a button, there are valid approaches to achieve this without breaking semantics.
How to Fix It
There are several strategies depending on your intent:
-
Use a <button> element styled as a heading. This is often the cleanest approach when the primary purpose is interactivity. You can style the button with CSS to match your heading appearance.
-
Wrap the heading in a <div> with role="button". This preserves the heading in the document outline while making the wrapper interactive. However, be aware that the button role applies role="presentation" to all descendant elements, meaning assistive technologies will strip the heading semantics from the <h2> inside it. The text content remains accessible, but it won’t be recognized as a heading.
-
Place a <button> inside the heading. This keeps the heading semantics intact for document structure while making the text inside it interactive. This pattern is commonly used for accordion-style components and is the approach recommended by the WAI-ARIA Authoring Practices.
Examples
❌ Invalid: role="button" on a heading
<h2 role="button">Toggle Section</h2>
This triggers the validation error because button is not an allowed role for heading elements.
✅ Fix: Use a <button> styled as a heading
<button type="button" class="heading-style">Toggle Section</button>
.heading-style {
font-size: 1.5em;
font-weight: bold;
background: none;
border: none;
cursor: pointer;
}
✅ Fix: Wrap the heading in a container with role="button"
<div role="button" tabindex="0">
<h2>Toggle Section</h2>
</div>
Note that this approach causes the <h2> to lose its heading semantics for assistive technologies, since the button role does not support semantic children. Also remember to add tabindex="0" so the element is keyboard-focusable, and implement keydown handlers for Enter and Space to replicate native button behavior.
✅ Fix: Place a <button> inside the heading (recommended for accordions)
<h2>
<button type="button" aria-expanded="false">
Toggle Section
</button>
</h2>
This is the most robust pattern. The heading remains in the document outline, and the button inside it is fully interactive with built-in keyboard support. Screen reader users can find it both when navigating by headings and when navigating by interactive elements. The aria-expanded attribute communicates whether the associated section is open or closed.
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 combobox role represents a composite widget that combines a text input with a popup (typically a listbox) that helps the user set the value of the input. It’s a common pattern seen in autocomplete fields, search suggestions, and dropdown selects with type-ahead functionality.
The reason the validator rejects role="combobox" on <input> is rooted in how the HTML specification defines allowed ARIA roles for each element. An <input type="text"> already carries an implicit role of textbox, and the spec only permits a limited set of explicit roles on it (such as combobox in ARIA 1.2 contexts, but W3C HTML validation may still flag this depending on the validator’s conformance rules). When the validator encounters a role that isn’t in its allowed list for that element, it raises this error.
Why This Matters
- Standards compliance: Using roles outside the permitted set for an element violates the HTML specification, which can lead to unpredictable behavior across browsers and assistive technologies.
- Accessibility: Assistive technologies like screen readers rely on correct role assignments to convey the widget’s purpose. A misapplied role can confuse users who depend on these tools, making the combobox harder to navigate or understand.
- Browser interop: Browsers may handle conflicting implicit and explicit roles inconsistently, leading to different experiences across platforms.
How to Fix It
There are two main approaches depending on which ARIA pattern you follow:
Approach 1: ARIA 1.1 Pattern (Container as Combobox)
In the ARIA 1.1 combobox pattern, the role="combobox" is placed on a container element (like a <div>) that wraps the <input>. This is the approach most likely to pass W3C validation without issues.
Approach 2: ARIA 1.2 Pattern (Input as Combobox)
ARIA 1.2 moved the combobox role directly onto the <input> element itself, which is a simpler and more widely adopted pattern in practice. However, the W3C HTML Validator may still flag this as an error if its conformance rules haven’t been updated to reflect ARIA 1.2. If passing validation is a strict requirement, use Approach 1.
Examples
❌ Incorrect: role="combobox" directly on <input>
This triggers the validation error:
<input type="text" role="combobox" aria-autocomplete="list">
✅ Correct: ARIA 1.1 pattern with container element
The role="combobox" is placed on the wrapping <div>, and the <input> inside it handles text entry:
<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" hidden>
<li role="option" id="opt-1">Apple</li>
<li role="option" id="opt-2">Banana</li>
<li role="option" id="opt-3">Cherry</li>
</ul>
Key attributes explained:
- role="combobox" on the <div> defines the overall widget for assistive technologies.
- aria-haspopup="listbox" tells screen readers that this widget has a popup of type listbox.
- aria-owns="suggestions" establishes an ownership relationship between the combobox and the listbox, even if they aren’t parent-child in the DOM.
- aria-expanded="false" indicates whether the popup is currently visible. Update this to "true" via JavaScript when the list is shown.
- aria-autocomplete="list" on the <input> signals that suggestions will be presented in a list as the user types.
- aria-controls="suggestions" on the <input> links it to the listbox it controls.
✅ Correct: ARIA 1.2 pattern using <input> with role="combobox" (if validation is not strict)
If your project follows ARIA 1.2 and you can tolerate or suppress the validation warning, this pattern is widely supported by modern browsers and screen readers:
<label for="fruit">Choose a fruit</label>
<input
id="fruit"
type="text"
role="combobox"
aria-autocomplete="list"
aria-expanded="false"
aria-controls="fruit-list"
aria-haspopup="listbox">
<ul id="fruit-list" role="listbox" hidden>
<li role="option" id="fruit-1">Apple</li>
<li role="option" id="fruit-2">Banana</li>
<li role="option" id="fruit-3">Cherry</li>
</ul>
This is the pattern recommended by the WAI-ARIA Authoring Practices Guide and is the most commonly implemented in modern component libraries. If validation compliance is required, wrap the input in a container and move the role="combobox" there as shown in Approach 1.
Whichever approach you choose, remember that ARIA attributes alone don’t create behavior — you’ll need JavaScript to toggle aria-expanded, manage focus, handle keyboard navigation, and update aria-activedescendant as the user moves through options.
The WAI-ARIA specification defines a strict set of valid role values, and "complimentary" is not among them. This is a straightforward typo — "complimentary" (meaning “expressing praise or given free of charge”) versus "complementary" (meaning “serving to complete or enhance something”). When a browser or assistive technology encounters an unrecognized role value, it ignores it. This means screen reader users lose the semantic meaning that the <aside> element would normally convey, making it harder for them to understand the page structure and navigate effectively.
The <aside> element already carries an implicit ARIA role of complementary as defined by the HTML specification. This means assistive technologies automatically treat <aside> as complementary content without any explicit role attribute. Adding role="complementary" to an <aside> is redundant. The simplest and best fix is to remove the misspelled role attribute and let the element’s native semantics do the work.
If you have a specific reason to explicitly set the role — for example, when overriding it with a different valid role — make sure the value is spelled correctly and is an appropriate role for the element.
Examples
❌ Incorrect: misspelled role value
<aside role="complimentary">
<h2>Related Articles</h2>
<ul>
<li><a href="/guide-one">Getting started guide</a></li>
<li><a href="/guide-two">Advanced techniques</a></li>
</ul>
</aside>
The value "complimentary" is not a valid ARIA role. Assistive technologies will ignore it, and the element loses its semantic meaning.
✅ Correct: remove the redundant role
<aside>
<h2>Related Articles</h2>
<ul>
<li><a href="/guide-one">Getting started guide</a></li>
<li><a href="/guide-two">Advanced techniques</a></li>
</ul>
</aside>
The <aside> element already implies role="complementary", so no explicit role is needed. This is the recommended approach.
✅ Correct: explicitly set the properly spelled role
<aside role="complementary">
<h2>Related Articles</h2>
<ul>
<li><a href="/guide-one">Getting started guide</a></li>
<li><a href="/guide-two">Advanced techniques</a></li>
</ul>
</aside>
This is valid but redundant. It may be appropriate in rare cases where you want to be explicit for clarity or to work around edge cases with certain assistive technologies.
Quick reference for similar typos
| Incorrect (typo) | Correct | Implicit on element |
|---|---|---|
| complimentary | complementary | <aside> |
| navagation | navigation | <nav> |
| presentaion | presentation | (none) |
Always double-check role values against the WAI-ARIA role definitions to ensure they are valid. When an HTML element already provides the semantics you need, prefer using the element without an explicit role — this follows the first rule of ARIA: “If you can use a native HTML element with the semantics and behavior you require, do so.”
The HTML specification defines a set of implicit ARIA roles (also called “native semantics”) for many HTML elements. The <dialog> element’s implicit role is dialog, which means assistive technologies like screen readers already announce it correctly without any explicit ARIA markup. When you add role="dialog" to a <dialog> element, you’re restating what the browser and accessibility tree already know—and the ARIA in HTML specification explicitly restricts this.
The ARIA in HTML spec maintains a list of allowed roles for each HTML element. For <dialog>, the only permitted role override is alertdialog (for dialogs that require an immediate response from the user). Setting role="dialog" is not listed as an allowed value because it duplicates the native semantics, and the spec treats such redundancy as a conformance error. This is why the W3C Validator reports: Bad value “dialog” for attribute “role” on element “dialog”.
Why this matters
- Standards compliance: The W3C Validator enforces the ARIA in HTML specification, which prohibits redundant role assignments on elements that already carry that role implicitly. Valid markup ensures your pages conform to web standards.
- Accessibility clarity: While most assistive technologies handle redundant roles gracefully today, unnecessary ARIA attributes add noise to the codebase and can cause confusion about whether the element’s native semantics are intentionally being overridden. The first rule of ARIA is: don’t use ARIA if a native HTML element already provides the semantics you need.
- Maintainability: Removing redundant attributes keeps your HTML clean and easier to maintain. Future developers won’t need to wonder whether the explicit role was added intentionally to work around a bug.
How to fix it
- Locate any <dialog> element with a role="dialog" attribute.
- Remove the role attribute entirely.
- If you need the dialog to behave as an alert dialog (one that interrupts the user and demands immediate attention), use role="alertdialog" instead—this is the one permitted role override for <dialog>.
Examples
Incorrect — redundant role causes a validation error
<dialog role="dialog">
<h2>Confirm action</h2>
<p>Are you sure you want to proceed?</p>
<button>Cancel</button>
<button>Confirm</button>
</dialog>
Correct — relying on the implicit role
<dialog>
<h2>Confirm action</h2>
<p>Are you sure you want to proceed?</p>
<button>Cancel</button>
<button>Confirm</button>
</dialog>
The <dialog> element automatically exposes role="dialog" in the accessibility tree, so no explicit attribute is needed.
Correct — using an allowed role override
If the dialog represents an urgent alert that requires immediate user interaction, you can override the role with alertdialog:
<dialog role="alertdialog" aria-labelledby="alert-title" aria-describedby="alert-desc">
<h2 id="alert-title">Session expiring</h2>
<p id="alert-desc">Your session will expire in 60 seconds. Do you want to continue?</p>
<button>Stay signed in</button>
</dialog>
This is valid because alertdialog is explicitly listed as a permitted role for the <dialog> element in the ARIA in HTML specification. Note that aria-labelledby and aria-describedby are strongly recommended for alert dialogs so assistive technologies can announce the title and description properly.
The W3C HTML specification restricts which ARIA roles can be used on specific elements. An li element already carries the implicit role of listitem when it’s a child of a ul or ol, so adding an explicit role is often unnecessary. The group role, as defined in the WAI-ARIA specification, is meant for container elements that form a logical collection of related items — similar to how a fieldset groups form controls. Applying group to an li element contradicts the element’s purpose as an individual item within a list.
This matters for several reasons. Assistive technologies like screen readers rely on ARIA roles to communicate the structure and purpose of content to users. When an li element is marked with role="group", it overrides the native listitem semantics, potentially confusing users who depend on these tools. A screen reader might announce the element as a group container rather than a list item, breaking the expected navigation pattern of the list. Browsers and assistive technologies are optimized around correct role usage, so invalid combinations can lead to unpredictable behavior.
When you might actually need group
If your intent is to group a set of related elements together, the group role belongs on a wrapping container — not on the individual items inside it. For example, you might use role="group" on a div or a ul that contains a subset of related controls within a larger interface. If an li contains nested interactive content that needs to be grouped, wrap that content in an inner container with the group role instead.
How to fix it
- Remove the role entirely if the li is a standard list item inside a ul or ol. The browser already provides the correct listitem semantics.
- Move the group role to a container element if you genuinely need to create a labeled group of related items.
- Use a different valid role if the li serves a non-standard purpose, such as menuitem, option, tab, treeitem, or presentation, depending on the widget pattern you’re building.
Examples
Incorrect: group role on li elements
<ul>
<li role="group">Fruits</li>
<li role="group">Vegetables</li>
<li role="group">Grains</li>
</ul>
This triggers the validation error because group is not a permitted role for li.
Correct: no explicit role needed
<ul>
<li>Fruits</li>
<li>Vegetables</li>
<li>Grains</li>
</ul>
Each li inside a ul automatically has the listitem role. No additional markup is needed.
Correct: using group on a container element
If you need to group related items with a label, apply the group role to the container:
<div role="group" aria-labelledby="food-heading">
<h2 id="food-heading">Food Categories</h2>
<ul>
<li>Fruits</li>
<li>Vegetables</li>
<li>Grains</li>
</ul>
</div>
Correct: nested groups within list items
If each list item contains a group of related controls, place the group role on an inner wrapper:
<ul>
<li>
<div role="group" aria-label="Text formatting">
<button>Bold</button>
<button>Italic</button>
<button>Underline</button>
</div>
</li>
<li>
<div role="group" aria-label="Text alignment">
<button>Left</button>
<button>Center</button>
<button>Right</button>
</div>
</li>
</ul>
This preserves the list structure while correctly applying the group role to containers of related widgets. The aria-label attribute gives each group an accessible name, which is recommended when using role="group".
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">
The section element carries implicit ARIA semantics — it maps to role="region" when given an accessible name. According to the ARIA in HTML specification, each HTML element has a set of roles it is allowed to assume, and list is not among the permitted roles for section. When the W3C validator encounters role="list" on a section, it flags this as a bad value because the role conflicts with the element’s intended purpose as a sectioning landmark.
This matters for several reasons. First, assistive technologies like screen readers rely on the correct pairing of elements and roles to convey page structure. A section announced as a list creates a confusing, contradictory experience — the underlying DOM says “this is a document section” while the ARIA role says “this is a list.” Second, browsers may handle conflicting semantics unpredictably, leading to inconsistent behavior across platforms. Third, the HTML specification explicitly defines which roles are valid on each element, and violating these rules means your markup is non-conforming.
The best fix is almost always to use native HTML list elements. The ul, ol, and li elements provide built-in list semantics that all browsers and assistive technologies understand without any extra ARIA attributes. Native elements also handle keyboard interaction and focus management correctly out of the box.
If your design requires a custom component where native list markup isn’t feasible — for example, a complex card grid that functions as a list — you can apply role="list" to a semantically neutral element like div or span. Each direct child acting as a list item should then receive role="listitem". This approach satisfies the ARIA specification while avoiding the element-role conflict.
If you actually need a section to group content within a page region and the list is just part of that section, nest a proper list inside the section rather than trying to make the section itself behave as a list.
Examples
❌ Bad: role="list" on a section element
This triggers the validation error because list is not an allowed role for section.
<section role="list">
<div>Item A</div>
<div>Item B</div>
</section>
✅ Fixed: Use native list elements (recommended)
Native lists provide the best accessibility support with no ARIA needed.
<ul>
<li>Item A</li>
<li>Item B</li>
</ul>
✅ Fixed: Nest a list inside the section
If you need the sectioning semantics of section alongside a list, nest the list inside it.
<section aria-labelledby="resources-heading">
<h2 id="resources-heading">Resources</h2>
<ul>
<li>Item A</li>
<li>Item B</li>
</ul>
</section>
✅ Fixed: ARIA roles on a neutral container
Use this only when native list markup is not possible, such as for highly custom UI components.
<div role="list">
<div role="listitem">Item A</div>
<div role="listitem">Item B</div>
</div>
✅ Fixed: Remove the role entirely
If the content isn’t actually a list, simply remove the role attribute and let the section carry its natural semantics.
<section>
<h2>Updates</h2>
<div>Item A</div>
<div>Item B</div>
</section>
The article element is a sectioning content element that represents a self-contained composition — a blog post, a news story, a forum entry, or similar independent content. Because it carries the implicit ARIA role of article, it functions as a landmark that assistive technologies recognize and expose to users for navigation. Overriding this landmark role with listitem creates a conflict: the element loses its article semantics for assistive technology users, and the listitem role itself becomes invalid because ARIA’s role mapping rules don’t permit it on article.
According to the ARIA in HTML specification, each HTML element has a set of allowed ARIA roles. The article element only permits a narrow set of roles (such as application, document, feed, main, none, presentation, and region). The listitem role is not among them. This restriction exists to prevent authors from creating confusing or contradictory semantics that would degrade the accessibility experience.
Beyond validation, this matters for real users. Screen readers rely on role information to convey structure. An article announced as a listitem is misleading — the user loses the ability to navigate by article landmarks, and the listitem may not function correctly without a proper parent list context. Browsers and assistive technologies expect listitem roles to appear as direct children of an element with role="list" (or within native ul/ol elements).
How to fix it
The solution depends on your intent:
- If you want a list of articles, use native HTML list markup (ul or ol with li) and place each article inside a list item. This gives you both list semantics and article landmarks without any ARIA overrides.
- If you can’t use native list elements, use neutral div elements with role="list" and role="listitem", and nest the article inside each listitem container.
- If the content isn’t truly a list, simply remove the role="listitem" attribute and let the article element use its native semantics.
Examples
Invalid: role="listitem" on an article element
This triggers the validator error because listitem is not an allowed role for article.
<article role="listitem">
<h2>News item</h2>
<p>Details about this story.</p>
</article>
Valid: native list with article elements inside
Using ul and li provides proper list semantics while preserving the article landmark role.
<ul>
<li>
<article>
<h2>News item</h2>
<p>Details about this story.</p>
</article>
</li>
<li>
<article>
<h2>Another item</h2>
<p>More details here.</p>
</article>
</li>
</ul>
Valid: ARIA list roles on neutral containers with nested article elements
When native list elements aren’t suitable for your layout, use div elements for the list structure and nest each article inside.
<div role="list">
<div role="listitem">
<article>
<h2>News item</h2>
<p>Details about this story.</p>
</article>
</div>
<div role="listitem">
<article>
<h2>Another item</h2>
<p>More details here.</p>
</article>
</div>
</div>
Valid: standalone article without a list role
If the content doesn’t belong in a list, simply remove the invalid role.
<article>
<h2>News item</h2>
<p>Details about this story.</p>
</article>
In every case, the key principle is the same: let the article element keep its native semantics, and use the appropriate list structure around it rather than forcing a conflicting role onto it.
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 HTML specification defines a specific list of allowed ARIA roles for each element. For the <img> element, role="none" is permitted but role="presentation" is not listed as a valid value. This distinction exists even though the WAI-ARIA 1.1 specification treats none and presentation as synonymous — role="none" was introduced as an alias specifically because the word “presentation” was often misunderstood by authors. The HTML spec adopted none as the canonical value for <img>.
Why this matters
Standards compliance: Using a role value not permitted by the HTML specification for a given element produces a validation error. Keeping your HTML valid ensures predictable behavior across browsers and assistive technologies.
Accessibility: The intended purpose of role="presentation" or role="none" is to tell assistive technologies that an element is purely decorative and carries no semantic meaning. However, for images, the established and most reliable way to achieve this is simply providing an empty alt attribute (alt=""). Screen readers already know to skip images with alt="", so adding a role is usually unnecessary.
Clarity of intent: Using alt="" clearly communicates to both browsers and developers that the image is decorative. If the image actually conveys information, it should have a meaningful alt value and no presentation-related role at all.
How to fix it
- If the image is decorative: Remove the role attribute entirely and ensure the image has alt="". This is the simplest and most widely supported approach.
- If you need an explicit ARIA role: Replace role="presentation" with role="none" and keep alt="".
- If the image conveys meaning: Remove the role and provide a descriptive alt attribute that explains what the image communicates.
Examples
❌ Bad: using role="presentation" on an <img>
<img src="divider.png" alt="" role="presentation">
This triggers the validation error because presentation is not an allowed role value for <img> in the HTML specification.
✅ Fixed: decorative image with empty alt (preferred)
<img src="divider.png" alt="">
The empty alt attribute is sufficient to tell assistive technologies the image is decorative. No role is needed.
✅ Fixed: decorative image with role="none"
<img src="divider.png" alt="" role="none">
If you explicitly need an ARIA role, role="none" is the valid value for <img>. The empty alt should still be included.
✅ Fixed: meaningful image with descriptive alt
<img src="quarterly-sales.png" alt="Bar chart showing quarterly sales increasing from $2M to $5M in 2024">
If the image communicates information, provide a descriptive alt and do not use a presentation or none role — doing so would hide the image’s meaning from assistive technology users.
❌ Bad: meaningful image incorrectly hidden
<img src="quarterly-sales.png" alt="Sales chart" role="presentation">
This is both invalid HTML (wrong role value for <img>) and an accessibility problem — the role would attempt to hide a meaningful image from screen readers.
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 rowgroup role represents a group of rows within a tabular structure — similar to what <thead>, <tbody>, or <tfoot> provide in a native HTML <table>. According to the ARIA specification, the rowgroup role should be used on elements that serve as structural containers for rows within a grid, table, or treegrid. A <header> element is not appropriate for this role because it carries its own strong semantic meaning (typically banner or a sectioning header), and overriding it with role="rowgroup" creates a conflict that the W3C validator flags.
This matters for several reasons. First, assistive technologies rely on correct role assignments to convey the structure of a page. When a <header> element is given role="rowgroup", screen readers receive contradictory signals about what the element represents, which can confuse users. Second, the HTML specification restricts which ARIA roles can be applied to certain elements — the <header> element does not allow rowgroup as a valid role override.
To fix this issue, you have two main options:
- Replace the <header> element with a <div> — since <div> has no implicit ARIA role, it freely accepts role="rowgroup".
- Use native HTML table elements — replace the custom ARIA table structure with <table>, <thead>, <tbody>, <tr>, <th>, and <td>, which provide the correct semantics without requiring any ARIA roles.
Examples
❌ Incorrect: role="rowgroup" on a <header> element
<div role="table" aria-label="Quarterly Sales">
<header role="rowgroup">
<div role="row">
<span role="columnheader">Quarter</span>
<span role="columnheader">Revenue</span>
</div>
</header>
<div role="rowgroup">
<div role="row">
<span role="cell">Q1</span>
<span role="cell">$1.2M</span>
</div>
</div>
</div>
The validator reports Bad value “rowgroup” for attribute “role” on element “header” because <header> cannot accept the rowgroup role.
✅ Fix option 1: Use a <div> instead of <header>
<div role="table" aria-label="Quarterly Sales">
<div role="rowgroup">
<div role="row">
<span role="columnheader">Quarter</span>
<span role="columnheader">Revenue</span>
</div>
</div>
<div role="rowgroup">
<div role="row">
<span role="cell">Q1</span>
<span role="cell">$1.2M</span>
</div>
</div>
</div>
A <div> is semantically neutral, so role="rowgroup" is perfectly valid on it.
✅ Fix option 2: Use native HTML table elements
<table>
<caption>Quarterly Sales</caption>
<thead>
<tr>
<th>Quarter</th>
<th>Revenue</th>
</tr>
</thead>
<tbody>
<tr>
<td>Q1</td>
<td>$1.2M</td>
</tr>
</tbody>
</table>
Native table elements like <thead> and <tbody> implicitly carry the rowgroup role, so no ARIA attributes are needed at all. This approach is generally preferred because it provides the best accessibility support out of the box and results in cleaner, more maintainable markup. Only use ARIA table roles when you cannot use native HTML tables (for example, when building a custom grid layout that requires non-table CSS).
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.
Ready to validate your sites?
Start your free trial today.