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 aria-hidden attribute controls whether an element and its descendants are exposed to assistive technologies such as screen readers. When set to true, the element is hidden from the accessibility tree; when set to false, it remains visible. According to the WAI-ARIA specification, the only valid values for this attribute are the literal strings true and false. Any other value — including "true" with embedded quotation marks — is invalid.
When the validator reports a bad value like "true" (with the quotation marks as part of the value), it means the actual attribute value contains the characters "true" rather than just true. HTML attributes already use outer quotes as delimiters, so any quotes inside the value become part of the value itself. The browser or assistive technology may not recognize "true" as a valid ARIA state, which can lead to the element being incorrectly exposed to or hidden from screen readers, breaking the intended accessibility behavior.
This issue commonly arises in a few scenarios:
- Copy-pasting from formatted text where “smart quotes” or extra quoting gets included.
- Templating engines or frameworks that double-escape or double-quote attribute values (e.g., aria-hidden="{{value}}" where {{value}} already outputs "true").
- JavaScript that sets the attribute with extra quotes, such as element.setAttribute("aria-hidden", '"true"').
To fix the issue, ensure the attribute value contains only the bare string true or false with no extra quotation marks, HTML entities, or escaped characters inside it.
Examples
Incorrect — extra quotes embedded in the value
<div aria-hidden='"true"'>
This content should be hidden from assistive tech
</div>
The rendered attribute value is literally "true" (five characters including the quotes), which is not a recognized ARIA value.
Incorrect — HTML entities producing extra quotes
<div aria-hidden=""true"">
This content should be hidden from assistive tech
</div>
The " entities resolve to quotation mark characters, producing the same invalid value of "true".
Correct — simple true value
<div aria-hidden="true">
This content is hidden from assistive tech
</div>
Correct — simple false value
<div aria-hidden="false">
This content is visible to assistive tech
</div>
Fixing the issue in JavaScript
If you’re setting the attribute dynamically, make sure you aren’t wrapping the value in extra quotes:
<div id="modal">Modal content</div>
<script>
// Incorrect:
// document.getElementById("modal").setAttribute("aria-hidden", '"true"');
// Correct:
document.getElementById("modal").setAttribute("aria-hidden", "true");
</script>
Fixing the issue in templating engines
If a template variable already outputs a quoted string, don’t add additional quotes around it. For example, in a templating system:
<!-- Incorrect: if myVar outputs "true" (with quotes) -->
<!-- <div aria-hidden="{{myVar}}"> -->
<!-- Correct: ensure myVar outputs just true (no quotes) -->
<div aria-hidden="true">
Content
</div>
The key takeaway is straightforward: the outer quotes in aria-hidden="true" are HTML syntax — they delimit the attribute value. The value itself must be exactly true or false with nothing extra. If you’re generating HTML dynamically, inspect the rendered output in your browser’s developer tools to confirm the attribute value doesn’t contain stray quotation marks.
The aria-current attribute indicates that an element represents the current item within a container or set of related elements. It is designed to convey to assistive technology users what sighted users already perceive through visual styling — for example, a highlighted link in a navigation bar or the active step in a multi-step wizard.
Because assistive technologies rely on a known set of token values to communicate meaning, using an invalid value means the attribute is effectively meaningless to screen readers and other tools. This undermines accessibility for the users who need it most. Browsers and assistive technologies will not interpret an unrecognized value as true; they may ignore the attribute or treat it as false, leading to a confusing experience.
Common mistakes that trigger this error include:
- Using an empty string: aria-current=""
- Using a custom or misspelled value: aria-current="active", aria-current="yes", aria-current="pgae"
- Dynamically setting the attribute to undefined or null as a string
Accepted values
Each accepted value carries a specific semantic meaning. Use the most descriptive one that matches your context:
- page — The current page within a set of pages (e.g., the active link in a breadcrumb or navigation menu).
- step — The current step within a process (e.g., the active step in a checkout flow).
- location — The current location within an environment or context (e.g., the highlighted node in a flow chart).
- date — The current date within a collection of dates (e.g., today’s date in a calendar).
- time — The current time within a set of times (e.g., the current time slot in a timetable).
- true — A generic indication that the element is the current item, when none of the more specific values apply.
- false — Explicitly indicates the element is not the current item. This is equivalent to omitting the attribute entirely.
Examples
❌ Invalid: empty string
An empty string is not a valid value for aria-current:
<nav>
<ol>
<li><a href="/step-1" aria-current="">Step 1</a></li>
<li><a href="/step-2">Step 2</a></li>
</ol>
</nav>
❌ Invalid: custom keyword
Values like "active" or "yes" are not recognized:
<nav>
<ul>
<li><a href="/" aria-current="active">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
✅ Fixed: using page for navigation
When marking the current page in a navigation menu, page is the most appropriate value:
<nav>
<ul>
<li><a href="/" aria-current="page">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
✅ Fixed: using step for a multi-step process
<ol>
<li><a href="/checkout/cart">Cart</a></li>
<li><a href="/checkout/shipping" aria-current="step">Shipping</a></li>
<li><a href="/checkout/payment">Payment</a></li>
</ol>
✅ Fixed: using true as a generic fallback
When none of the specific token values fit, use true:
<ul>
<li><a href="/item-1" aria-current="true">Item 1</a></li>
<li><a href="/item-2">Item 2</a></li>
<li><a href="/item-3">Item 3</a></li>
</ul>
✅ Fixed: removing the attribute instead of setting false
If an element is not the current item, simply omit aria-current rather than setting it to false or an empty string. Both of the following are valid, but omitting the attribute is cleaner:
<!-- Valid but unnecessary -->
<a href="/about" aria-current="false">About</a>
<!-- Preferred: just omit it -->
<a href="/about">About</a>
Tips for dynamic frameworks
If you’re using a JavaScript framework that conditionally sets aria-current, make sure the attribute is either set to a valid value or removed from the DOM entirely. Avoid patterns that render aria-current="" or aria-current="undefined" when the element is not current. In React, for instance, you can pass undefined (not the string "undefined") to omit the attribute:
<!-- What your framework should render for the current page -->
<a href="/" aria-current="page">Home</a>
<!-- What it should render for non-current pages (no attribute at all) -->
<a href="/about">About</a>
The aria-required attribute is a WAI-ARIA property that signals to assistive technologies (like screen readers) that a user must provide a value for a form control before the form can be submitted. According to the ARIA specification, the attribute’s value must be either "true" or "false". Any other value — such as "yes", "1", "", or a misspelling — is invalid and will produce this validation error.
Common mistakes include writing aria-required="yes", aria-required="", aria-required="required", or even aria-required="True" (note: the value is case-sensitive and must be lowercase).
Why this matters
When an invalid value is used, assistive technologies may not correctly interpret whether the field is required. This can lead to a confusing experience for users who rely on screen readers, as they may not be told that a field is mandatory before submitting a form. Using valid attribute values ensures consistent, predictable behavior across all browsers and assistive technologies.
When to use aria-required vs. required
For native HTML form elements like <input>, <select>, and <textarea>, you should use the built-in HTML required attribute. It provides both validation behavior and accessibility information out of the box, without needing ARIA.
The aria-required attribute is intended for custom (non-semantic) form controls — for example, a <div> with a role="textbox" or role="combobox". In these cases, the browser doesn’t know the element is a form control, so aria-required="true" communicates the requirement to assistive technologies.
Examples
❌ Invalid values for aria-required
<!-- "yes" is not a valid value -->
<div
role="textbox"
contenteditable
aria-labelledby="name_label"
aria-required="yes">
</div>
<!-- Empty string is not valid -->
<input type="text" aria-required="">
<!-- "required" is not valid -->
<input type="email" aria-required="required">
✅ Correct usage with aria-required
<div id="email_label">Email Address *</div>
<div
role="textbox"
contenteditable
aria-labelledby="email_label"
aria-required="true"
id="email">
</div>
✅ Explicitly marking a field as not required
<div id="notes_label">Notes (optional)</div>
<div
role="textbox"
contenteditable
aria-labelledby="notes_label"
aria-required="false"
id="notes">
</div>
✅ Preferred approach for native form elements
When using standard HTML form controls, skip aria-required and use the native required attribute instead:
<label for="email">Email Address *</label>
<input type="email" id="email" name="email" required>
<label for="country">Country *</label>
<select id="country" name="country" required>
<option value="">Select a country</option>
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
</select>
The native required attribute automatically conveys the required state to assistive technologies and also triggers built-in browser validation, making it the better choice whenever a native form element is available.
Boolean attributes in HTML work differently from regular attributes. Their mere presence on an element makes them “true,” and their absence makes them “false.” According to the WHATWG HTML specification, a boolean attribute may only have three valid representations:
- The attribute name alone (e.g., disabled)
- The attribute with an empty string value (e.g., disabled="")
- The attribute with its own name as the value (e.g., disabled="disabled")
Any other value — including seemingly intuitive ones like "true", "yes", or "no" — is invalid and will cause the W3C HTML Validator to report an error such as: Bad value “disabled” for attribute “disabled” on element “input” (or a similar message referencing whatever invalid value you used).
Why this matters
Standards compliance: Using invalid values violates the HTML specification, which can lead to unpredictable behavior as browsers evolve.
Misleading behavior: A common pitfall is writing disabled="false" and expecting the input to be enabled. This does not work as expected — because the attribute is still present, the element remains disabled regardless of the value. This can lead to confusing bugs where developers think they’re enabling a field but it stays disabled.
Accessibility: Assistive technologies rely on the DOM’s interpretation of boolean attributes. While browsers typically handle invalid values gracefully by treating any present disabled attribute as true, sticking to valid values ensures the most consistent behavior across screen readers and other tools.
Templating and frameworks: This issue frequently arises when templating engines or JavaScript frameworks insert string values into boolean attributes. If your template outputs disabled="true" or disabled="false", you should instead conditionally include or omit the attribute entirely.
How to fix it
- Remove the value entirely — just write the attribute name by itself.
- Use an empty string — write disabled="" if your tooling requires an explicit value.
- Use the canonical form — write disabled="disabled" if you need XHTML compatibility.
- To enable an element, remove the disabled attribute completely rather than setting it to "false".
Examples
Incorrect usage
These all trigger a validation error because the values are not valid for a boolean attribute:
<input type="text" disabled="yes">
<input type="text" disabled="true">
<input type="text" disabled="false">
<input type="text" disabled="1">
<button disabled="no">Submit</button>
Note that disabled="false" and disabled="no" still disable the element — the browser sees the attribute is present and treats it as true.
Correct usage
All three of these are valid ways to disable an input:
<input type="text" disabled>
<input type="text" disabled="">
<input type="text" disabled="disabled">
To have an enabled input, simply omit the attribute:
<input type="text">
Handling dynamic values in JavaScript
If you need to toggle the disabled state dynamically, use the DOM property rather than setting an attribute value:
<form>
<input type="text" id="username">
<button type="button" id="toggle">Toggle</button>
<script>
document.getElementById("toggle").addEventListener("click", function () {
var input = document.getElementById("username");
input.disabled = !input.disabled;
});
</script>
</form>
Setting element.disabled = true or element.disabled = false in JavaScript correctly adds or removes the attribute from the DOM without producing invalid markup.
Other boolean attributes
This same rule applies to all boolean attributes in HTML, including checked, readonly, required, hidden, autoplay, loop, muted, and others. For example:
<!-- Incorrect -->
<input type="checkbox" checked="true">
<input type="email" required="required_field">
<!-- Correct -->
<input type="checkbox" checked>
<input type="email" required>
When in doubt, use the simplest form: just the attribute name with no value. It’s the most readable, the most concise, and fully compliant with the HTML specification.
In HTML, boolean attributes like required work differently from what many developers expect. Their presence on an element means the value is true, and their absence means the value is false. According to the WHATWG HTML specification, a boolean attribute’s value must either be the empty string or an ASCII case-insensitive match for the attribute’s canonical name. For the required attribute, this means the only valid values are "" (empty string) and "required".
A common mistake is writing required="true" or required="false". The value "true" is not a valid boolean attribute value in HTML and will trigger this validation error. Even more confusingly, writing required="false" does not make the input optional — since the attribute is still present, the browser still treats the field as required. This can lead to subtle bugs where a form field appears to be optional in your code but is actually enforced as required by the browser.
This matters for several reasons:
- Standards compliance: Invalid attribute values violate the HTML specification and will cause W3C validation errors.
- Code clarity: Using non-standard values like "true" or "false" misleads other developers about how the attribute works.
- Unexpected behavior: required="false" still makes the field required, which can cause confusing form behavior.
To make a field optional, simply remove the required attribute entirely rather than setting it to "false".
Examples
Incorrect: Invalid values for required
These will all trigger the “Bad value for attribute required“ validation error:
<input type="text" required="true">
<input type="email" required="false">
<input type="text" required="yes">
<input type="text" required="1">
Correct: Valid uses of the required attribute
All three of these forms are valid and make the input required:
<!-- Preferred: no value (most concise) -->
<input type="text" required>
<!-- Also valid: empty string -->
<input type="text" required="">
<!-- Also valid: canonical name as value -->
<input type="text" required="required">
Correct: Making a field optional
To make a field optional, remove the attribute entirely:
<input type="text">
Full form example
<form action="/submit" method="post">
<label for="name">Name (required):</label>
<input type="text" id="name" name="name" required>
<label for="notes">Notes (optional):</label>
<input type="text" id="notes" name="notes">
<button type="submit">Submit</button>
</form>
This same rule applies to other boolean attributes in HTML, such as disabled, checked, readonly, multiple, and autofocus. They all follow the same pattern: use the attribute with no value, with an empty string, or with the attribute’s own name as the value. Never assign "true" or "false" to them.
ARIA (Accessible Rich Internet Applications) works as a system where roles define what an element is, and states and properties describe the element’s current condition or characteristics. Certain ARIA attributes are only valid when used on elements that have a specific role — either explicitly declared via the role attribute or implicitly provided by the HTML element itself. When you add an ARIA state or property to a generic element like a <div> or <span> without specifying a role, assistive technologies have no context for interpreting that attribute. For example, aria-expanded="true" on a plain <div> tells a screen reader that something is expanded, but it doesn’t communicate what is expanded — is it a button, a navigation menu, a tree item? The role provides that crucial context.
This matters for several reasons:
- Accessibility: Screen readers and other assistive technologies rely on the combination of roles and their associated states/properties to convey meaningful information to users. An ARIA property without a role is ambiguous and can lead to a confusing experience.
- Standards compliance: The WAI-ARIA specification defines which states and properties are allowed on which roles. Using an ARIA attribute outside of a valid role context violates the spec.
- Predictable behavior: Browsers and assistive technologies may handle orphaned ARIA attributes inconsistently, leading to unpredictable results across different platforms.
To fix this issue, you have two approaches:
- Add an explicit role attribute to the element, choosing a role that supports the ARIA attributes you’re using.
- Use a semantic HTML element that already has an implicit ARIA role. For instance, <nav> has an implicit role of navigation, <button> has an implicit role of button, and <header> has an implicit role of banner. This is generally the preferred approach, as it provides built-in keyboard interaction and semantics without extra effort.
When choosing a role, make sure the ARIA states and properties you’re using are actually supported by that role. For example, aria-expanded is supported by roles like button, combobox, link, treeitem, and others — but not by every role. Consult the WAI-ARIA roles documentation to verify compatibility.
Examples
Invalid: ARIA property without a role
This <div> uses aria-expanded but has no role, so the validator doesn’t know what kind of element this is supposed to be.
<div aria-expanded="true">
Menu contents
</div>
Fixed: Adding an explicit role
Adding role="button" tells assistive technologies that this is a button that can be expanded or collapsed.
<div role="button" aria-expanded="true" tabindex="0">
Menu contents
</div>
Fixed: Using a semantic HTML element instead
A <button> element already has an implicit button role, so no explicit role attribute is needed. This is the preferred approach.
<button aria-expanded="true">
Toggle menu
</button>
Invalid: aria-label on a generic element
A <span> has no implicit role, so aria-label has no meaningful context here.
<span aria-label="Close dialog">X</span>
Fixed: Using a semantic element or adding a role
<button aria-label="Close dialog">X</button>
Or, if you need to use a <span>:
<span role="button" tabindex="0" aria-label="Close dialog">X</span>
Using elements with implicit roles
Many HTML elements already carry implicit ARIA roles, so adding ARIA states and properties to them is valid without an explicit role attribute:
<!-- <nav> has implicit role="navigation" -->
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<!-- <details> supports aria-expanded implicitly -->
<details aria-describedby="help-text">
<summary>More information</summary>
<p id="help-text">Additional details about this topic.</p>
</details>
As a general rule, always prefer native semantic HTML elements over generic elements with ARIA roles. Native elements come with built-in keyboard support, focus management, and accessibility semantics — reducing the amount of custom code you need to write and maintain.
The aria-controls attribute establishes a programmatic relationship between a controlling element (like a button, tab, or scrollbar) and the element it controls (like a panel, region, or content area). Assistive technologies such as screen readers use this relationship to help users navigate between related elements — for example, announcing that a button controls a specific panel and allowing the user to jump to it.
When the id referenced in aria-controls doesn’t exist in the document, the relationship is broken. Screen readers may attempt to locate the target element and fail silently, or they may announce a control relationship that leads nowhere. This degrades the experience for users who rely on assistive technology and violates the WAI-ARIA specification, which requires that the value of aria-controls be a valid ID reference list pointing to elements in the same document.
Common causes of this error include:
- Typos in the id or the aria-controls value.
- Dynamically generated content where the controlled element hasn’t been rendered yet or has been removed from the DOM.
- Copy-paste errors where aria-controls was copied from another component but the corresponding id was not updated.
- Referencing elements in iframes or shadow DOM, which are considered separate document contexts.
The aria-controls attribute accepts one or more space-separated ID references. Every listed ID must match an element in the same document.
How to Fix
- Verify the target element exists in the document and has the exact id that aria-controls references.
- Check for typos — ID matching is case-sensitive, so mainPanel and mainpanel are not the same.
- If the controlled element is added dynamically, ensure it is present in the DOM before or at the same time as the controlling element, or update aria-controls programmatically when the target becomes available.
- If the controlled element is genuinely absent (e.g., conditionally rendered), remove the aria-controls attribute until the target element exists.
Examples
Incorrect: aria-controls references a non-existent ID
<button aria-controls="info-panel" aria-expanded="false">
Toggle Info
</button>
<div id="infopanel">
<p>Here is some additional information.</p>
</div>
This triggers the error because aria-controls="info-panel" does not match the actual id of "infopanel" (note the missing hyphen).
Correct: aria-controls matches an existing element’s ID
<button aria-controls="info-panel" aria-expanded="false">
Toggle Info
</button>
<div id="info-panel">
<p>Here is some additional information.</p>
</div>
Correct: Tab and tab panel relationship
<div role="tablist">
<button role="tab" aria-controls="tab1-panel" aria-selected="true">
Overview
</button>
<button role="tab" aria-controls="tab2-panel" aria-selected="false">
Details
</button>
</div>
<div id="tab1-panel" role="tabpanel">
<p>Overview content goes here.</p>
</div>
<div id="tab2-panel" role="tabpanel" hidden>
<p>Details content goes here.</p>
</div>
Both aria-controls values — tab1-panel and tab2-panel — correctly correspond to elements present in the document.
Correct: Custom scrollbar controlling a region
<div role="scrollbar" aria-controls="main-content" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" aria-orientation="vertical"></div>
<div id="main-content" role="region" aria-label="Main content">
<p>Scrollable content goes here.</p>
</div>
Correct: Controlling multiple elements
The aria-controls attribute can reference multiple IDs separated by spaces. Each ID must exist in the document.
<button aria-controls="section-a section-b">
Expand All Sections
</button>
<div id="section-a">
<p>Section A content.</p>
</div>
<div id="section-b">
<p>Section B content.</p>
</div>
The aria-describedby attribute is a core part of WAI-ARIA, the Web Accessibility Initiative’s specification for making web content more accessible. It works by creating a relationship between an element and one or more other elements that provide additional descriptive text. Screen readers and other assistive technologies use this relationship to announce the descriptive text when a user interacts with the element.
When you set aria-describedby="some-id", the browser looks for an element with id="some-id" in the same document. If no matching element exists, the reference is broken. This means assistive technologies cannot find the description, and the attribute silently does nothing. The W3C validator flags this as an error because a dangling reference indicates a bug — either the referenced element was removed, renamed, or was never added.
This issue commonly arises due to:
- Typos in the id value — the aria-describedby value doesn’t match the target element’s id exactly (the match is case-sensitive).
- Dynamic content — the described-by element is rendered conditionally or injected by JavaScript after validation.
- Copy-paste errors — markup was copied from another page or component, but the referenced element wasn’t included.
- Refactoring — an element’s id was changed or the element was removed, but the aria-describedby reference wasn’t updated.
Multiple id values can be listed in aria-describedby, separated by spaces. Every single id in that list must resolve to an element in the document. If even one is missing, the validator will report an error for that reference.
How to fix it
- Check for typos. Compare the value in aria-describedby against the id of the target element. Remember that id matching is case-sensitive — helpText and helptext are different.
- Add the missing element. If the descriptive element doesn’t exist yet, create it with the matching id.
- Remove stale references. If the description is no longer needed, remove the aria-describedby attribute entirely rather than leaving a broken reference.
- Verify all IDs in a multi-value list. If aria-describedby contains multiple space-separated IDs, confirm each one exists.
Examples
Broken reference (triggers the error)
In this example, aria-describedby points to password-help, but no element with that id exists in the document:
<label for="password">Password</label>
<input type="password" id="password" aria-describedby="password-help">
Fixed by adding the referenced element
Adding an element with id="password-help" resolves the issue:
<label for="password">Password</label>
<input type="password" id="password" aria-describedby="password-help">
<p id="password-help">Must be at least 8 characters with one number.</p>
Broken reference due to a typo
Here the aria-describedby value uses a different case than the element’s id:
<input type="text" id="email" aria-describedby="emailHelp">
<small id="emailhelp">We'll never share your email.</small>
The fix is to make the id values match exactly:
<input type="text" id="email" aria-describedby="email-help">
<small id="email-help">We'll never share your email.</small>
Multiple IDs with one missing
When listing multiple descriptions, every id must be present:
<!-- "format-hint" exists but "length-hint" does not — this triggers the error -->
<input type="text" id="username" aria-describedby="format-hint length-hint">
<span id="format-hint">Letters and numbers only.</span>
Fix it by adding the missing element:
<input type="text" id="username" aria-describedby="format-hint length-hint">
<span id="format-hint">Letters and numbers only.</span>
<span id="length-hint">Between 3 and 20 characters.</span>
Removing the attribute when no description is needed
If the descriptive content has been removed and is no longer relevant, simply remove the aria-describedby attribute:
<input type="text" id="search">
Every HTML semantic element carries an implicit ARIA role that assistive technologies already recognize. The <article> element has a built-in role of article, which signals that the content represents a self-contained composition — such as a blog post, news story, forum comment, or any section that could be independently distributed or reused. When you explicitly add role="article" to an <article> element, you’re telling the browser and screen readers something they already know.
While this redundancy won’t break anything functionally, it creates unnecessary noise in your markup and goes against the W3C’s guidance on using ARIA. The first rule of ARIA use states: “If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of repurposing an element and adding an ARIA role, state or property to make it accessible, then do so.” Redundant roles make code harder to maintain and can signal to other developers that something non-standard is happening when it isn’t.
The role="article" attribute is useful when applied to non-semantic elements like <div> or <span> that need to convey article semantics — for instance, in legacy codebases where changing the element isn’t feasible. But on the <article> element itself, it should simply be removed.
Examples
❌ Redundant role on <article>
This triggers the validator warning because role="article" duplicates the element’s implicit role:
<article role="article">
<h2>Breaking News</h2>
<p>A rare bird was spotted in the city park this morning.</p>
</article>
✅ Fixed: no explicit role needed
Simply remove the role attribute. The <article> element already communicates the article role to assistive technologies:
<article>
<h2>Breaking News</h2>
<p>A rare bird was spotted in the city park this morning.</p>
</article>
✅ Appropriate use of role="article" on a non-semantic element
If you cannot use the <article> element for some reason, applying the role to a generic element like <div> is valid and useful:
<div role="article">
<h2>Breaking News</h2>
<p>A rare bird was spotted in the city park this morning.</p>
</div>
✅ Multiple articles within a feed
A common pattern is nesting several <article> elements inside a feed. No explicit roles are needed on the articles themselves:
<section role="feed" aria-label="Latest posts">
<article>
<h2>First Post</h2>
<p>Content of the first post.</p>
</article>
<article>
<h2>Second Post</h2>
<p>Content of the second post.</p>
</article>
</section>
This same principle applies to other semantic elements with implicit roles — for example, <nav> already has role="navigation", <main> has role="main", and <header> has role="banner". Avoid adding redundant roles to any of these elements to keep your HTML clean and standards-compliant.
The HTML specification defines built-in semantic roles for many elements, and the <header> element is one of them. When a <header> is a direct child of <body> (or at least not nested inside a sectioning element), browsers and assistive technologies already interpret it as a banner landmark — the region of the page that typically contains the site logo, navigation, and other introductory content. Explicitly adding role="banner" duplicates what the browser already knows, which adds unnecessary noise to your markup.
This principle is part of the WAI-ARIA specification’s guidance on using ARIA roles: the first rule of ARIA is “If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state, or property to make it accessible, then do so.” Redundant roles don’t typically break anything, but they clutter the code, can confuse developers maintaining the project, and signal a misunderstanding of HTML semantics.
It’s worth noting an important nuance: the <header> element only maps to the banner role when it is not a descendant of <article>, <aside>, <main>, <nav>, or <section>. When nested inside one of these sectioning elements, <header> has no corresponding landmark role — it simply serves as the header for that particular section. In that context, adding role="banner" would not be redundant; it would actually change the semantics, which is almost certainly not what you want.
To fix the warning, remove the role="banner" attribute from your <header> element. The native semantics are sufficient.
Examples
Incorrect: redundant role="banner" on <header>
This triggers the validator warning because <header> already implies the banner role at the top level:
<header role="banner">
<img src="logo.svg" alt="My Company">
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
Correct: let <header> use its implicit role
Simply remove the role="banner" attribute:
<header>
<img src="logo.svg" alt="My Company">
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
Correct: using role="banner" on a non-<header> element
If for some reason you cannot use a <header> element (e.g., working within a legacy CMS), applying role="banner" to a <div> is the appropriate way to convey the same landmark semantics:
<div role="banner">
<img src="logo.svg" alt="My Company">
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</div>
A <header> inside a sectioning element has no banner role
When <header> is nested inside an <article> or other sectioning element, it does not carry the banner role. This is expected and correct — the <header> here simply introduces the article content:
<article>
<header>
<h2>Article Title</h2>
<p>Published on <time datetime="2024-01-15">January 15, 2024</time></p>
</header>
<p>Article content goes here.</p>
</article>
Every HTML element carries an implicit ARIA role that communicates its purpose to assistive technologies like screen readers. The <button> element natively has the button role built in, so explicitly adding role="button" is redundant. The W3C validator flags this as unnecessary because it adds no information — assistive technologies already understand that a <button> is a button.
The role attribute exists primarily to assign interactive semantics to elements that don’t have them natively. For example, you might add role="button" to a <div> or <span> that has been styled and scripted to behave like a button (though using a native <button> is always preferable). When you apply it to an element that already carries that role by default, it creates noise in your code and can signal to other developers that something unusual is going on — when in fact nothing is.
This principle applies broadly across HTML. Other examples of redundant roles include role="link" on an <a> element with an href, role="navigation" on a <nav> element, and role="heading" on an <h1> through <h6> element. The WAI-ARIA specification refers to these as “default implicit ARIA semantics,” and the general rule is: don’t set an ARIA role that matches the element’s native semantics.
Removing redundant roles keeps your markup clean, easier to maintain, and avoids potential confusion during code reviews or audits. It also aligns with the first rule of ARIA: “If you can use a native HTML element with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state, or property to make it accessible, then do so.”
How to fix it
Remove the role="button" attribute from any <button> element. No replacement is needed — the native semantics are already correct.
If you have a non-button element (like a <div>) that uses role="button", consider replacing it with a real <button> element instead. This gives you built-in keyboard support, focus management, and form submission behavior for free.
Examples
❌ Redundant role on a button
<button role="button">Buy now</button>
<button type="submit" role="button">Submit</button>
Both of these trigger the validator warning because role="button" duplicates what the <button> element already communicates.
✅ Button without redundant role
<button>Buy now</button>
<button type="submit">Submit</button>
Simply removing the role attribute resolves the issue. The element’s native semantics handle everything.
❌ Using role=”button” on a non-semantic element
<div role="button" tabindex="0" onclick="handleClick()">Buy now</div>
While this is technically valid and won’t trigger the same warning, it requires manual handling of keyboard events, focus styles, and accessibility states.
✅ Using a native button instead
<button onclick="handleClick()">Buy now</button>
A native <button> provides keyboard interaction (Enter and Space key activation), focusability, and correct role announcement — all without extra attributes or JavaScript.
Other common redundant roles to avoid
<!-- ❌ Redundant -->
<a href="/about" role="link">About</a>
<nav role="navigation">...</nav>
<h1 role="heading">Title</h1>
<input type="checkbox" role="checkbox">
<!-- ✅ Clean -->
<a href="/about">About</a>
<nav>...</nav>
<h1>Title</h1>
<input type="checkbox">
The WAI-ARIA specification defines implicit roles (also called “native semantics”) for many HTML elements. An <input> element with type="submit" inherently communicates to assistive technologies that it is a button control. Adding role="button" explicitly restates what the browser and screen readers already know, making it redundant.
The role="button" attribute is designed for situations where you need to make a non-interactive element — such as a <div> or <span> — behave like a button for assistive technologies. When applied to elements that already carry this semantic meaning natively, it adds unnecessary noise to your markup without providing any accessibility benefit.
Why this is a problem
- Redundancy: The explicit role duplicates the element’s built-in semantics, cluttering the HTML with no added value.
- Maintenance risk: Redundant ARIA attributes can mislead other developers into thinking the role is necessary, or that the element’s native semantics differ from what they actually are.
- Standards compliance: The W3C validator flags this as an issue because the ARIA in HTML specification explicitly states that authors should not set ARIA roles or attributes that match an element’s implicit native semantics. This principle is sometimes called the “first rule of ARIA” — don’t use ARIA when a native HTML element already provides the semantics you need.
- Potential conflicts: While current browsers handle redundant roles gracefully, explicitly overriding native semantics can theoretically interfere with future browser or assistive technology behavior.
How to fix it
Remove the role="button" attribute from any <input type="submit"> element. The same principle applies to other input types with implicit roles, such as <input type="reset"> (which also has an implicit button role) and <button> elements.
Examples
❌ Incorrect: redundant role="button" on a submit input
<form action="/checkout" method="post">
<input type="submit" role="button" value="Buy Now">
</form>
✅ Correct: no explicit role needed
<form action="/checkout" method="post">
<input type="submit" value="Buy Now">
</form>
❌ Incorrect: redundant role on a <button> element
The same issue applies to <button> elements, which also have an implicit button role:
<button type="submit" role="button">Submit Order</button>
✅ Correct: let native semantics do the work
<button type="submit">Submit Order</button>
✅ Correct: using role="button" where it is appropriate
The role="button" attribute is meaningful when applied to an element that does not natively convey button semantics. Note that you must also handle keyboard interaction and focus management manually in this case:
<div role="button" tabindex="0">Add to Cart</div>
Even in this scenario, using a native <button> element is strongly preferred over adding ARIA roles to non-interactive elements, since the native element provides built-in keyboard support and focus behavior for free.
The <summary> element serves as the clickable disclosure toggle for a <details> element. Because its built-in behavior is inherently interactive — clicking it expands or collapses the parent <details> content — the HTML specification assigns it an implicit button role. This means assistive technologies like screen readers already announce <summary> as a button without any additional markup.
When you explicitly add role="button" to a <summary> element, the W3C validator flags it as unnecessary. While this doesn’t cause functional problems, redundant ARIA roles are discouraged by the first rule of ARIA use: if an HTML element already has the semantics you need, don’t re-add them with ARIA attributes. Redundant roles add noise to your code, can confuse other developers into thinking custom behavior is being applied, and in edge cases may interact unexpectedly with certain assistive technologies.
This principle applies broadly — many HTML elements have implicit roles (e.g., <nav> has navigation, <main> has main, <button> has button). Adding the role they already carry is always unnecessary.
How to fix it
Remove the role="button" attribute from the <summary> element. No replacement is needed since the semantics are already built in.
Examples
❌ Incorrect: redundant role="button" on <summary>
<details>
<summary role="button">Show more information</summary>
<p>Here is the additional information that was hidden.</p>
</details>
The validator will report: The “button” role is unnecessary for element “summary”.
✅ Correct: <summary> without an explicit role
<details>
<summary>Show more information</summary>
<p>Here is the additional information that was hidden.</p>
</details>
The <summary> element’s implicit button role ensures assistive technologies already treat it as an interactive control. No additional attributes are required.
✅ Correct: a more complete <details> example
<details>
<summary>I have keys but no doors. I have space but no room. You can enter but can't leave. What am I?</summary>
<p>A keyboard.</p>
</details>
Clicking the <summary> toggles the parent <details> element between its open and closed states. Screen readers announce it as a button automatically, and keyboard users can activate it with <kbd>Enter</kbd> or <kbd>Space</kbd> — all without any explicit ARIA role.
The HTML specification maps certain elements to implicit ARIA roles. The <footer> element, when used as a direct child of <body> (i.e., not nested inside an <article>, <aside>, <main>, <nav>, or <section> element), automatically carries the contentinfo landmark role. This means screen readers and other assistive technologies already announce it as a content information landmark without any extra markup.
Adding role="contentinfo" to a <footer> element is redundant because:
- It duplicates built-in semantics. Browsers already expose the correct role to the accessibility tree. Repeating it adds no benefit and clutters your markup.
- It can cause confusion for developers. Seeing an explicit role might suggest the element doesn’t have one by default, leading to misunderstandings about how HTML semantics work.
- It violates the first rule of ARIA use. The W3C’s “Using ARIA” guide states: “If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.”
It’s worth noting that when a <footer> is nested inside a sectioning element like <article> or <section>, it does not carry the contentinfo role — it maps to a generic role instead. In that context, adding role="contentinfo" would actually change the element’s semantics rather than being redundant, though doing so is generally not appropriate since each page should have only one contentinfo landmark.
If you are working with a <div> that serves as a footer (perhaps in legacy code), the best approach is to replace it with a semantic <footer> element rather than applying role="contentinfo" to the <div>.
Examples
❌ Redundant role on <footer>
This triggers the validator warning because the role is already implicit:
<footer role="contentinfo">
<p>© 2024 Example Corp. All rights reserved.</p>
</footer>
✅ Fixed: <footer> without redundant role
Simply remove the role="contentinfo" attribute:
<footer>
<p>© 2024 Example Corp. All rights reserved.</p>
</footer>
✅ Using a <div> as a footer (legacy pattern)
If you cannot use a <footer> element for some reason, applying role="contentinfo" to a <div> is valid and meaningful since the <div> has no implicit role:
<div role="contentinfo">
<p>© 2024 Example Corp. All rights reserved.</p>
</div>
However, replacing the <div> with a <footer> is always the preferred approach.
✅ Nested footer inside a section
When <footer> appears inside a sectioning element, it does not carry the contentinfo role. No explicit role is needed here either — it simply represents footer content for that section:
<article>
<h2>Blog Post Title</h2>
<p>Article content here.</p>
<footer>
<p>Published on January 1, 2024</p>
</footer>
</article>
The <dialog> element was introduced to provide a native way to create modal and non-modal dialog boxes in HTML. As defined in the WHATWG HTML Living Standard and the ARIA in HTML specification, every <dialog> element automatically carries an implicit dialog role. This means assistive technologies like screen readers already recognize it as a dialog without any additional ARIA markup.
When you explicitly add role="dialog" to a <dialog> element, you’re restating what the browser and assistive technologies already know. This violates the first rule of ARIA use: do not use ARIA if you can use a native HTML element or attribute with the semantics already built in. While this redundancy won’t break functionality, it clutters your markup and signals to other developers (and validators) that the author may not understand the element’s built-in semantics.
This principle applies broadly across HTML. Many elements have implicit ARIA roles — <nav> has navigation, <main> has main, <button> has button, and so on. Adding the matching role explicitly to any of these elements produces a similar validator warning.
How to fix it
Simply remove the role="dialog" attribute from the <dialog> element. The built-in semantics handle everything automatically. If you need to provide additional context for assistive technologies, consider using aria-label or aria-labelledby to give the dialog a descriptive accessible name — that’s genuinely useful supplementary information rather than a redundant role.
Examples
Incorrect: redundant role attribute
<dialog role="dialog">
<h2>Confirm action</h2>
<p>Are you sure you want to delete this item?</p>
<button>Cancel</button>
<button>Delete</button>
</dialog>
This triggers the validator warning because role="dialog" duplicates the implicit role of the <dialog> element.
Correct: relying on implicit semantics
<dialog>
<h2>Confirm action</h2>
<p>Are you sure you want to delete this item?</p>
<button>Cancel</button>
<button>Delete</button>
</dialog>
Correct: adding a descriptive accessible name
<dialog aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirm action</h2>
<p>Are you sure you want to delete this item?</p>
<button>Cancel</button>
<button>Delete</button>
</dialog>
Using aria-labelledby to associate the dialog with its heading is a meaningful enhancement — it gives the dialog an accessible name that screen readers announce when the dialog opens. This is the kind of ARIA usage that genuinely improves accessibility, as opposed to redundantly restating the element’s role.
The role="button" attribute tells assistive technologies like screen readers that an element behaves as a button — a widget used to perform actions such as submitting a form, opening a dialog, or triggering a command. When a <button> element appears inside an element with role="button", the result is a nested interactive control. The HTML specification explicitly forbids this because interactive content must not be nested within other interactive content.
This nesting causes real problems. Screen readers may announce the outer element as a button but fail to recognize or reach the inner <button>. Keyboard users may not be able to focus on or activate the inner control. Different browsers handle the situation inconsistently — some may ignore one of the controls entirely, others may fire events on the wrong element. The end result is an interface that is broken for many users.
This issue commonly arises in a few scenarios:
- A <div> or <span> is given role="button" and then a <button> is placed inside it for styling or click-handling purposes.
- A component library wraps content in a role="button" container, and a developer adds a <button> inside without realizing the conflict.
- A custom card or list item is made clickable with role="button", but also contains action buttons within it.
The fix depends on your intent. If the outer element is the intended interactive control, remove the inner <button> and handle interactions on the outer element. If the inner <button> is the intended control, remove role="button" from the ancestor. If both need to be independently clickable, restructure the markup so neither is a descendant of the other.
Examples
❌ Incorrect: <button> inside an element with role="button"
<div role="button" tabindex="0" onclick="handleClick()">
<button type="button">Click me</button>
</div>
This is invalid because the <button> is a descendant of the <div> that has role="button".
✅ Fix option 1: Use only the <button> element
If the inner <button> is the actual control, remove role="button" from the wrapper:
<div>
<button type="button" onclick="handleClick()">Click me</button>
</div>
✅ Fix option 2: Use only the outer role="button" element
If the outer element is the intended interactive control, remove the inner <button>:
<div role="button" tabindex="0" onclick="handleClick()">
Click me
</div>
Note that when using role="button" on a non-<button> element, you must also handle keyboard events (Enter and Space) manually. A native <button> provides this for free, so prefer option 1 when possible.
❌ Incorrect: Clickable card containing action buttons
<div role="button" tabindex="0" class="card">
<h3>Item title</h3>
<p>Description text</p>
<button type="button">Delete</button>
</div>
✅ Fix: Separate the card link from the action buttons
<div class="card">
<h3><button type="button" class="card-link">Item title</button></h3>
<p>Description text</p>
<button type="button">Delete</button>
</div>
In this approach, the card’s main action is handled by a <button> on the title, while the “Delete” button remains an independent control. Neither is nested inside the other, and both are accessible to keyboard and screen reader users.
Many HTML elements come with built-in ARIA roles that assistive technologies already recognize. The <fieldset> element is one of these — its implicit role is group, which tells screen readers that the contained form controls are related. When you add role="group" to a <fieldset>, you’re telling the browser something it already knows.
This redundancy matters for a few reasons:
- Code cleanliness: Unnecessary attributes add clutter, making your markup harder to read and maintain.
- ARIA best practices: The first rule of ARIA is “If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.” Adding role="group" to <fieldset> violates this principle in spirit — it suggests the developer may not understand the element’s native semantics.
- Potential confusion: Explicitly setting roles that match the default can mislead other developers into thinking the role is doing something special, or that removing it would change behavior.
This same principle applies to other elements with implicit roles, such as role="navigation" on <nav>, role="banner" on <header>, or role="button" on <button>. If the element already carries the semantic meaning natively, there’s no need to duplicate it with an explicit ARIA role.
To fix this, simply remove the role="group" attribute from the <fieldset> element. No replacement is needed — the browser and assistive technologies will continue to treat the <fieldset> as a group automatically.
Examples
Incorrect: redundant role="group" on <fieldset>
<form>
<fieldset role="group">
<legend>Shipping Address</legend>
<label for="street">Street:</label>
<input type="text" id="street" name="street">
<label for="city">City:</label>
<input type="text" id="city" name="city">
</fieldset>
</form>
The validator will report that the group role is unnecessary for the <fieldset> element.
Correct: <fieldset> without explicit role
<form>
<fieldset>
<legend>Shipping Address</legend>
<label for="street">Street:</label>
<input type="text" id="street" name="street">
<label for="city">City:</label>
<input type="text" id="city" name="city">
</fieldset>
</form>
The <fieldset> element inherently communicates the group role to assistive technologies, so no ARIA attribute is needed.
When role on <fieldset> is appropriate
There are cases where you might legitimately set a different role on a <fieldset> — for example, role="radiogroup" when the fieldset contains a set of related radio buttons and you want to convey more specific semantics:
<form>
<fieldset role="radiogroup" aria-labelledby="color-legend">
<legend id="color-legend">Favorite Color</legend>
<label><input type="radio" name="color" value="red"> Red</label>
<label><input type="radio" name="color" value="blue"> Blue</label>
<label><input type="radio" name="color" value="green"> Green</label>
</fieldset>
</form>
This is valid because radiogroup is a different role that provides more specific meaning than the default group. The validator only warns when the explicit role matches the element’s implicit role.
HTML heading elements (<h1> through <h6>) have built-in semantic meaning that browsers and assistive technologies already understand. According to the WAI-ARIA specification, each of these elements carries an implicit heading role with a corresponding aria-level — <h1> has aria-level="1", <h2> has aria-level="2", and so on. When you explicitly add role="heading" to one of these elements, you’re telling the browser something it already knows, which clutters your markup without providing any benefit.
This pattern is part of a broader principle in ARIA authoring known as the first rule of ARIA: don’t use ARIA when a native HTML element already provides the semantics you need. Redundant ARIA roles can cause confusion for developers maintaining the code, as it suggests that the role might be necessary or that the element might not otherwise be recognized as a heading. In some edge cases, adding an explicit aria-level that doesn’t match the heading level (e.g., aria-level="3" on an <h1>) can create conflicting information for screen readers, leading to an inconsistent experience for users of assistive technologies.
The role="heading" attribute is designed for situations where you need to give heading semantics to a non-heading element, such as a <div> or <span>. In those cases, you must also include the aria-level attribute to specify the heading’s level. However, whenever possible, using native heading elements is always preferred over this ARIA-based approach.
How to fix it
- Remove role="heading" from any <h1> through <h6> element.
- Remove aria-level if it was added alongside the redundant role and matches the heading’s native level.
- If you genuinely need a non-standard element to act as a heading, use role="heading" with aria-level on that element instead — but prefer native heading elements whenever possible.
Examples
❌ Redundant role on a native heading
<h1 role="heading" aria-level="1">Welcome to My Site</h1>
<h2 role="heading">About Us</h2>
<h3 role="heading" aria-level="3">Our Mission</h3>
All three headings will trigger the validator warning. The role="heading" and aria-level attributes are completely unnecessary here because the elements already convey this information natively.
✅ Native headings without redundant roles
<h1>Welcome to My Site</h1>
<h2>About Us</h2>
<h3>Our Mission</h3>
Simply removing the redundant attributes resolves the issue while preserving full accessibility.
✅ Correct use of the heading role on a non-heading element
In rare cases where you cannot use a native heading element, the heading role is appropriate on a generic element:
<div role="heading" aria-level="2">Section Title</div>
This tells assistive technologies to treat the <div> as a level-2 heading. Note that aria-level is required here since a <div> has no implicit heading level. That said, using a native <h2> is always the better choice:
<h2>Section Title</h2>
❌ Conflicting aria-level on a native heading
Be especially careful with this anti-pattern, where the explicit level contradicts the element:
<h1 role="heading" aria-level="3">Page Title</h1>
This sends mixed signals — the element is an <h1> but claims to be level 3. Screen readers may behave unpredictably. If you need a level-3 heading, use <h3>:
<h3>Page Title</h3>
Every HTML element has an implicit ARIA role defined by the HTML specification. The <img> element’s implicit role is img, which means assistive technologies like screen readers already recognize it as an image without any additional ARIA attributes. Adding role="img" explicitly doesn’t change behavior — it just adds unnecessary noise to your markup and signals that the author may not understand how native semantics work.
The W3C validator flags this because it violates the first rule of ARIA: don’t use ARIA if you can use a native HTML element or attribute that already has the semantics you need. Redundant roles clutter your code, make maintenance harder, and can confuse other developers into thinking the role is there for a specific reason.
The role="img" attribute is genuinely useful in other contexts — for example, when you want to group multiple elements together and have them treated as a single image by assistive technologies. A <div> or <span> has no implicit img role, so adding role="img" to a container is meaningful and appropriate.
How to fix it
Simply remove the role="img" attribute from any <img> element. The image semantics are already built in. Make sure you still provide a meaningful alt attribute for accessibility.
Examples
❌ Redundant role on <img>
<img src="photo.jpg" alt="A sunset over the ocean" role="img">
The validator will warn: The “img” role is unnecessary for element “img”.
✅ Fixed: Remove the redundant role
<img src="photo.jpg" alt="A sunset over the ocean">
No explicit role is needed. The browser already communicates this element as an image.
✅ Legitimate use of role="img" on a non-image element
The role="img" attribute is appropriate when applied to a container that groups multiple elements into a single conceptual image:
<div role="img" aria-label="Star rating: 4 out of 5">
<span>⭐</span>
<span>⭐</span>
<span>⭐</span>
<span>⭐</span>
<span>☆</span>
</div>
Here, the <div> has no inherent image semantics, so role="img" is meaningful — it tells assistive technologies to treat the entire group as a single image described by the aria-label.
✅ Another legitimate use: CSS background image with role="img"
<div role="img" aria-label="Company logo" class="logo-background"></div>
Since a <div> styled with a CSS background image has no image semantics, role="img" paired with aria-label ensures the visual content is accessible.
Many HTML elements have built-in (implicit) ARIA roles defined by the WAI-ARIA specification. The <li> element natively carries the listitem role when it is a child of a <ul>, <ol>, or <menu> element. Adding role="listitem" explicitly doesn’t change behavior, but it clutters your markup and signals a misunderstanding of how semantic HTML and ARIA interact. This falls under the first rule of ARIA use: “If you can use a native HTML element with the semantics and behavior you require already built in, do so, instead of re-purposing an element and adding an ARIA role.”
Redundant ARIA roles create several problems:
- Maintenance burden — Extra attributes add noise to your code, making it harder to read and maintain.
- Potential confusion — Other developers may wonder if the explicit role was added intentionally to override something, leading to uncertainty during code reviews.
- Validator warnings — Tools like the W3C HTML Validator flag these redundancies, and accumulating unnecessary warnings can obscure real issues that need attention.
The ARIA listitem role is designed for situations where you cannot use semantic HTML — for instance, when you need to create a list-like structure from generic elements like <div> or <span>. In those cases, you would pair role="list" on the container with role="listitem" on each child. But when you’re already using <ul>, <ol>, or <menu> with <li> children, the ARIA roles are built in and should not be repeated.
To fix this, simply remove the role="listitem" attribute from your <li> elements. If you also have role="list" on a <ul> or <ol>, remove that too — it’s equally redundant.
Examples
❌ Redundant role on <li> elements
<ul role="list">
<li role="listitem">Apples</li>
<li role="listitem">Bananas</li>
<li role="listitem">Cherries</li>
</ul>
Both role="list" on the <ul> and role="listitem" on each <li> are unnecessary because these elements already carry those roles implicitly.
✅ Clean semantic HTML without redundant roles
<ul>
<li>Apples</li>
<li>Bananas</li>
<li>Cherries</li>
</ul>
The <ul> and <li> elements provide all the accessibility semantics needed without any explicit ARIA attributes.
✅ Using ARIA roles on non-semantic elements (when necessary)
If for some reason you cannot use native list elements, ARIA roles are appropriate on generic elements:
<div role="list">
<div role="listitem">Apples</div>
<div role="listitem">Bananas</div>
<div role="listitem">Cherries</div>
</div>
This is the intended use case for role="listitem" — adding list semantics to elements that don’t have them natively. However, using semantic <ul>/<ol> with <li> is always preferred when possible.
The ARIA specification defines a set of roles that convey the purpose of an element to assistive technologies like screen readers. Many HTML elements have implicit ARIA roles — built-in semantics that map directly to ARIA roles without any extra markup. The <main> element is one of these: it automatically communicates the main landmark role to assistive technologies.
When you write <main role="main">, you’re explicitly stating something the browser and assistive technologies already know. The W3C validator warns about this redundancy because it can signal a misunderstanding of how native HTML semantics work. While it won’t break anything, unnecessary attributes add noise to your markup and can make code harder to maintain.
This principle applies broadly across HTML. For example, <nav> implicitly has role="navigation", <header> implicitly has role="banner" (when not nested inside a sectioning element), and <button> implicitly has role="button". Explicitly restating these roles is discouraged by both the W3C and the ARIA in HTML specification, which states: “Setting an ARIA role and/or `aria-` attribute that matches the implicit ARIA semantics is unnecessary and is NOT RECOMMENDED.”*
Why this matters
- Code clarity: Redundant attributes make your HTML harder to read and can confuse other developers into thinking the attribute is necessary.
- Standards compliance: The W3C validator raises a warning, which can obscure more important issues in your validation reports.
- Best practices: Following the principle of using native HTML semantics without redundant ARIA keeps your code clean and aligns with the first rule of ARIA: “If you can use a native HTML element with the semantics and behavior you require already built in, do so, instead of re-purposing an element and adding an ARIA role.”
How to fix it
Remove the role="main" attribute from any <main> element. The semantic meaning is already provided by the element itself.
If you’re working with a <div> or another generic element that needs the main landmark role (for example, in a legacy codebase that cannot use <main>), then role="main" is appropriate and necessary on that element.
Examples
❌ Redundant role on <main>
<main role="main">
<h1>Welcome to my site</h1>
<p>This is the primary content of the page.</p>
</main>
The role="main" attribute is unnecessary here because <main> already implies it.
✅ Using <main> without a redundant role
<main>
<h1>Welcome to my site</h1>
<p>This is the primary content of the page.</p>
</main>
✅ Using role="main" on a non-semantic element (when necessary)
<div role="main">
<h1>Welcome to my site</h1>
<p>This is the primary content of the page.</p>
</div>
This approach is valid when you cannot use the <main> element — for instance, due to framework constraints or legacy browser support requirements. In most modern projects, prefer the <main> element instead.
The HTML specification defines certain elements as having implicit ARIA roles — roles that are automatically communicated to assistive technologies without any additional attributes. The nav element is one of these: its implicit role is navigation. When you explicitly add role="navigation" to a nav element, you’re telling the browser something it already knows, which clutters your markup without adding any value.
This redundancy matters for several reasons:
- Code maintainability: Unnecessary attributes make your HTML harder to read and maintain. Future developers may wonder if the explicit role is there for a specific reason, creating confusion.
- Standards compliance: The W3C validator warns about this because the ARIA specification follows a principle often summarized as the first rule of ARIA: don’t use ARIA if a native HTML element already provides the semantics you need. Extending this principle, don’t re-declare semantics that are already present.
- No accessibility benefit: Assistive technologies like screen readers already recognize nav as a navigation landmark. Adding the explicit role doesn’t improve the experience for users of these technologies — it’s simply noise.
The role="navigation" attribute is useful when applied to a non-semantic element like a div or span that functions as navigation but can’t be changed to a nav element (for example, due to legacy constraints). But when you’re already using nav, the attribute is unnecessary.
To fix this, remove the role="navigation" attribute from your nav element. The semantic meaning is fully preserved.
Examples
Incorrect: redundant role on nav
This triggers the W3C validator warning because the navigation role is already implicit:
<nav role="navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
Correct: nav without the explicit role
Simply remove the redundant role attribute:
<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: using role="navigation" on a non-semantic element
If you cannot use a nav element, applying the role to a div is a valid approach. This does not trigger the warning:
<div role="navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</div>
Correct: labeling multiple nav elements
When a page has more than one nav, use aria-label or aria-labelledby to differentiate them for assistive technology users — but still don’t add the redundant role:
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</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>
The HTML specification and WAI-ARIA guidelines establish that certain HTML elements carry implicit landmark roles. The <section> element implicitly maps to role="region", meaning assistive technologies like screen readers already recognize it as a region landmark without any additional ARIA markup. This principle is captured by the first rule of ARIA use: “If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.”
Adding role="region" to a <section> doesn’t change the element’s behavior or how assistive technologies interpret it — it simply duplicates what the browser already communicates. The W3C Validator warns about this redundancy to encourage cleaner, more maintainable markup and to help developers understand native HTML semantics.
This same principle applies to other HTML elements with implicit roles: <nav> has an implicit role="navigation", <main> has role="main", <aside> has role="complementary", <header> has role="banner" (when not nested in a sectioning element), and <footer> has role="contentinfo" (when not nested in a sectioning element). Adding these explicit roles to their corresponding elements will trigger similar validator warnings.
It’s worth noting that a <section> element is only exposed as a region landmark by assistive technologies when it has an accessible name. If your <section> doesn’t have an accessible name (via aria-label, aria-labelledby, or similar mechanisms), screen readers may not treat it as a navigable landmark — but this still doesn’t mean you should add role="region", since the implicit role mapping remains the same regardless.
How to fix it
- Remove the role="region" attribute from any <section> element.
- If you want the section to be a meaningful landmark for screen reader users, give it an accessible name using aria-labelledby (pointing to a heading) or aria-label.
- Never add explicit ARIA roles that duplicate the implicit role of a native HTML element.
Examples
Incorrect: redundant role on section
<section role="region">
<h2>Contact Information</h2>
<p>Email us at info@example.com</p>
</section>
Correct: section without redundant role
<section>
<h2>Contact Information</h2>
<p>Email us at info@example.com</p>
</section>
Correct: section with an accessible name for landmark navigation
Using aria-labelledby to associate the section with its heading ensures assistive technologies expose it as a named landmark region:
<section aria-labelledby="contact-heading">
<h2 id="contact-heading">Contact Information</h2>
<p>Email us at info@example.com</p>
</section>
Correct: section with aria-label when no visible heading exists
<section aria-label="Contact information">
<p>Email us at info@example.com</p>
</section>
Incorrect: redundant roles on other landmark elements
The same principle applies to other native landmark elements. Avoid these patterns:
<nav role="navigation">
<a href="/">Home</a>
</nav>
<main role="main">
<p>Page content</p>
</main>
<aside role="complementary">
<p>Related links</p>
</aside>
Correct: landmark elements without redundant roles
<nav>
<a href="/">Home</a>
</nav>
<main>
<p>Page content</p>
</main>
<aside>
<p>Related links</p>
</aside>
Every HTML element carries an implicit ARIA role based on its type and attributes. For <input type="search"> elements that do not have a list attribute, the browser automatically exposes the element with the searchbox role to assistive technologies. This mapping is defined in the ARIA in HTML specification, which establishes the correspondence between native HTML semantics and ARIA roles.
When you explicitly add role="searchbox" to an element that already carries that role implicitly, the validator raises a warning because the attribute is doing nothing useful. While it won’t break functionality, redundant roles clutter your markup and can signal to other developers (or future you) that something special is intended when it isn’t. Following the general principle of ARIA — “don’t use ARIA if you can use native HTML” — also means not restating what the browser already communicates.
Note the distinction the validator makes: this applies specifically to <input type="search"> elements without a list attribute. When a list attribute is present (linking the input to a <datalist>), the implicit role changes to combobox, so in that specific scenario the implicit role is different. However, for a plain search input without list, the searchbox role is already baked in.
Why it matters
- Standards compliance: The W3C validator flags redundant roles to encourage clean, semantic markup that relies on native HTML behavior.
- Maintainability: Redundant attributes add noise. Other developers may wonder why the role was explicitly set and whether removing it would break something.
- ARIA best practices: The first rule of ARIA is to use native HTML semantics whenever possible. Restating implicit roles goes against this principle and can mask situations where an explicit role would actually be meaningful.
How to fix it
Simply remove the role="searchbox" attribute from any <input type="search"> element that does not have a list attribute. The browser and assistive technologies will continue to treat it as a search box.
Examples
Incorrect — redundant role
The role="searchbox" is unnecessary here because <input type="search"> already implies it:
<label for="site-search">Search the site:</label>
<input type="search" id="site-search" role="searchbox" placeholder="Search...">
Correct — relying on implicit role
Remove the redundant role attribute and let native HTML semantics do the work:
<label for="site-search">Search the site:</label>
<input type="search" id="site-search" placeholder="Search...">
Correct — explicit role when implicit role differs
When a list attribute is present, the implicit role changes to combobox. If you want assistive technologies to treat it as a searchbox instead, an explicit role is justified:
<label for="city-search">Search cities:</label>
<input type="search" id="city-search" list="cities" role="searchbox">
<datalist id="cities">
<option value="Amsterdam">
<option value="Berlin">
<option value="Cairo">
</datalist>
In this case, the validator will not flag the role as redundant because the implicit role (combobox) differs from the explicitly set role (searchbox).
When you use semantic HTML elements, browsers automatically assign appropriate ARIA roles behind the scenes. An <input type="text"> element without a list attribute is inherently recognized by browsers and assistive technologies as a textbox — a control that accepts free-form text input. Explicitly declaring role="textbox" on such an element repeats information that is already conveyed natively, which is what the validator flags.
The distinction about the list attribute matters because when an <input type="text"> does have a list attribute (linking it to a <datalist>), its implicit role changes to combobox rather than textbox. In that scenario, a role="textbox" would not only be redundant — it would actually be incorrect. The validator’s message specifically targets the case where there is no list attribute, meaning the implicit role is already textbox.
Why this is a problem
- Redundancy clutters your code. Adding roles that elements already possess makes HTML harder to read and maintain without providing any benefit.
- Potential for confusion. Other developers (or your future self) may wonder if the explicit role was added intentionally to override some other behavior, leading to unnecessary investigation.
- Standards compliance. The W3C and WAI-ARIA authoring practices recommend against setting ARIA roles that duplicate the native semantics of an element. The first rule of ARIA use is: “If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.”
- No accessibility benefit. Assistive technologies already understand that <input type="text"> is a textbox. The explicit role adds no additional information for screen readers or other tools.
How to fix it
Simply remove the role="textbox" attribute from your <input type="text"> element. The native semantics of the element are sufficient.
If you’ve added the role because the input is styled or behaves differently, consider whether you actually need a different element or a different ARIA pattern instead.
Examples
❌ Incorrect: redundant role="textbox"
<label for="username">Username</label>
<input type="text" id="username" role="textbox">
The role="textbox" is unnecessary here because <input type="text"> without a list attribute already has an implicit role of textbox.
✅ Correct: no explicit role needed
<label for="username">Username</label>
<input type="text" id="username">
✅ Also correct: input with list attribute (different implicit role)
<label for="color">Favorite color</label>
<input type="text" id="color" list="colors">
<datalist id="colors">
<option value="Red">
<option value="Green">
<option value="Blue">
</datalist>
In this case, the list attribute changes the implicit role to combobox, so the validator warning about a redundant textbox role would not apply. Note that adding role="textbox" here would be incorrect rather than merely redundant, since it would override the proper combobox semantics.
❌ Incorrect: redundant role on implicit text input
<label for="search-field">Search</label>
<input id="search-field" role="textbox">
When the type attribute is omitted, <input> defaults to type="text", so the implicit role is still textbox and the explicit role remains redundant.
✅ Correct: let the default type handle semantics
<label for="search-field">Search</label>
<input id="search-field">
Ready to validate your sites?
Start your free trial today.