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 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
roleattribute entirely, or usearia-hidden="true"to explicitly hide the element from the accessibility tree. - Meaningful icons (that convey information visually): Use
role="img"along with anaria-labelto 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 anaria-label.
Examples
❌ Invalid: Using the non-existent "icon" role
<spanclass="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.
<spanclass="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:
<spanclass="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>
<spanclass="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:
<buttonaria-label="Save">
<spanclass="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:
<imgsrc="icon-alert.svg"alt="Alert"class="icon">
For decorative image icons, use an empty alt attribute:
<imgsrc="icon-decorative.svg"alt=""class="icon">
This error occurs when you use role="presentational" on an HTML element. While the intent is clear — you want to strip the element's implicit ARIA semantics — the value presentational does not exist in the WAI-ARIA specification. It's a common typo or misconception. The two valid roles for this purpose are presentation and none, which are synonyms of each other.
When you apply role="presentation" or role="none" to an element, you're telling assistive technologies (like screen readers) to ignore the element's implicit semantic meaning. For example, a <table> used purely for layout purposes has no tabular data semantics, so role="presentation" removes the table-related semantics from the accessibility tree. However, the content inside the element remains accessible — only the container's semantics (and in some cases, its required associated descendants) are removed.
This matters for several reasons:
- Standards compliance: Using an invalid role value means the attribute is essentially ignored by browsers and assistive technologies, so the element's semantics are not removed as intended.
- Accessibility: If a screen reader encounters an unknown role, it may fall back to the element's default semantics, leading to a confusing experience. For instance, a layout table without a valid
presentationrole 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
<tablerole="presentational">
<tr>
<td>Column 1</td>
<td>Column 2</td>
</tr>
</table>
✅ Correct: using presentation
<tablerole="presentation">
<tr>
<td>Column 1</td>
<td>Column 2</td>
</tr>
</table>
✅ Correct: using none (synonym for presentation)
<tablerole="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 -->
<imgsrc="divider.png"alt=""role="presentational">
<!-- ✅ Correct -->
<imgsrc="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:
<tablerole="none presentation">
<tr>
<td>Layout content</td>
</tr>
</table>
This fallback pattern ensures that older assistive technologies that don't recognize none will still pick up presentation. However, in most modern environments, either value alone is sufficient.
The search ARIA role is a landmark role, which means it identifies a large, navigable section of a page — specifically, the region that contains the search functionality. Landmark roles help assistive technologies (like screen readers) quickly identify and jump to major sections of a document. Because landmarks describe sections of a page, they belong on container elements that encompass all the parts of the search interface (the label, the input field, the submit button, etc.), not on a single <input> element.
When you place role="search" on an <input>, the validator rejects it because the search role doesn't match the semantics of an input control. An <input> represents a single interactive widget, not a page region. The valid way to indicate that an input field is for search queries is to use <input type="search">, which gives browsers and assistive technologies the correct semantic meaning for that specific control.
Meanwhile, if you want to mark an entire search form as a search landmark, apply role="search" to the <form> element that wraps the search controls. In modern HTML, you can also use the <search> element, which has the implicit search landmark role without needing any ARIA attribute.
How to fix it
- Remove
role="search"from the<input>element. - Change the input's
typeto"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>
<labelfor="query">Search</label>
<inputrole="search"id="query"name="q">
<buttontype="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
<formrole="search">
<labelfor="query">Search this site</label>
<inputtype="search"id="query"name="q">
<buttontype="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>
<labelfor="query">Search this site</label>
<inputtype="search"id="query"name="q">
<buttontype="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":
<labelfor="filter">Filter results</label>
<inputtype="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
<sectionrole="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:
<sectionrole="navigation"aria-label="Main navigation">
<ul>
<li><ahref="/">Home</a></li>
<li><ahref="/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 -->
<sectionaria-labelledby="features-heading">
<h2id="features-heading">Features</h2>
<p>Explore our product features.</p>
</section>
<!-- Also valid but redundant -->
<sectionrole="region"aria-labelledby="features-heading">
<h2id="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.
ARIA defines a closed set of role tokens. Browsers and assistive technologies rely on this fixed list to determine how an element should be announced and how it fits into the page's landmark structure. When a role value falls outside that list, the attribute is effectively ignored. A screen reader encountering role="sidebar" will not recognize the element as a landmark, so users who navigate by landmarks will skip right past it. Automated accessibility testing tools will also flag it as an error, and the validator will reject it outright.
The concept most people mean when they write role="sidebar" is ancillary or tangential content related to the main content of the page. ARIA calls this the complementary role. In semantic HTML, the <aside> element maps to complementary without any explicit role attribute. Using <aside> is the simplest fix and keeps the markup clean.
A few things to keep in mind when choosing the replacement:
- If the sidebar contains related content, promotional links, or supplementary information,
complementary(via<aside>orrole="complementary") is correct. - If the sidebar is actually a set of navigation links, use
<nav>orrole="navigation"instead. Pick the role that matches the content's purpose, not its visual position. - When a page has more than one
complementaryregion, each one needs a distinct accessible name so screen reader users can tell them apart. Usearia-labelledbypointing to a visible heading, oraria-labelif no heading is present. - Do not add
role="complementary"to an<aside>element. The implicit mapping already handles it, and the redundant attribute adds noise without benefit.
Examples
Invalid: non-existent ARIA role
This triggers the validator error because sidebar is not a defined ARIA role.
<divrole="sidebar">
<h2>Related links</h2>
<ul>
<li><ahref="/guide-a">Guide A</a></li>
<li><ahref="/guide-b">Guide B</a></li>
</ul>
</div>
Fixed: use role="complementary" on a generic container
If you need to keep the <div>, assign the correct ARIA role and provide an accessible name.
<divrole="complementary"aria-labelledby="sidebar-title">
<h2id="sidebar-title">Related links</h2>
<ul>
<li><ahref="/guide-a">Guide A</a></li>
<li><ahref="/guide-b">Guide B</a></li>
</ul>
</div>
Fixed: use <aside> for semantic HTML
The <aside> element has an implicit complementary role, so no role attribute is needed.
<asidearia-labelledby="sidebar-title">
<h2id="sidebar-title">Related links</h2>
<ul>
<li><ahref="/guide-a">Guide A</a></li>
<li><ahref="/guide-b">Guide B</a></li>
</ul>
</aside>
Multiple complementary regions with distinct labels
When a page has more than one <aside>, give each a unique accessible name so users can distinguish them.
<asidearia-labelledby="filters-title">
<h2id="filters-title">Filter results</h2>
<!-- filter controls -->
</aside>
<asidearia-labelledby="related-title">
<h2id="related-title">Related articles</h2>
<!-- related links -->
</aside>
When the content is navigation, not complementary
A sidebar that contains section links or site links is navigation, not complementary content. Use <nav> instead.
<navaria-label="Section navigation">
<ul>
<li><ahref="#intro">Intro</a></li>
<li><ahref="#examples">Examples</a></li>
<li><ahref="#contact">Contact</a></li>
</ul>
</nav>
Valid ARIA landmark roles
For reference, these are the ARIA landmark roles that browsers and assistive technologies recognize:
banner(implicit on<header>when not nested inside a sectioning element)navigation(implicit on<nav>)main(implicit on<main>)complementary(implicit on<aside>)contentinfo(implicit on<footer>when not nested inside a sectioning element)region(implicit on<section>when it has an accessible name)search(implicit on<search>)form(implicit on<form>when it has an accessible name)
Stick to these defined values. Inventing role names like sidebar, content, or wrapper will always produce a validation error and provide no accessibility benefit.
ARIA defines a fixed set of role values that user agents and assistive technologies understand. sidebar is not in that set, so role="sidebar" fails conformance checking and gives unreliable signals to screen readers. Using a valid role or the correct HTML element improves accessibility, ensures consistent behavior across browsers and AT, and keeps your markup standards‑compliant.
Sidebars typically contain tangential or ancillary content (e.g., related links, promos, author info). The ARIA role that matches that meaning is complementary. In HTML, the semantic element for the same concept is aside, which by default maps to the complementary landmark in accessibility APIs. Prefer native semantics first: use <aside> when possible. Only add role="complementary" when you can’t change the element type or when you need an explicit landmark for non-semantic containers.
How to fix:
- If the element is a sidebar: change
<div role="sidebar">to<aside>(preferred), or to<div role="complementary">. - Ensure each page has at most one primary
mainregion and that complementary regions are not essential to understanding the main content. - Provide an accessible name for the complementary region when multiple exist, using
aria-labeloraria-labelledby, to help users navigate landmarks.
Examples
Triggers the validator error
<divrole="sidebar">
<!-- Sidebar content -->
</div>
Fixed: use the semantic element (preferred)
<asidearia-label="Related articles">
<!-- Sidebar content -->
</aside>
Fixed: keep the container, apply a valid role
<divrole="complementary"aria-label="Related articles">
<!-- Sidebar content -->
</div>
Full document example with two sidebars (each labeled)
<!doctype html>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>Sidebar Landmarks Example</title>
</head>
<body>
<header>
<h1>News Today</h1>
</header>
<mainid="main">
<article>
<h2>Main Story</h2>
<p>...</p>
</article>
</main>
<asidearia-label="Trending topics">
<ul>
<li>Science</li>
<li>Politics</li>
<li>Sports</li>
</ul>
</aside>
<divrole="complementary"aria-labelledby="sponsor-title">
<h2id="sponsor-title">Sponsored</h2>
<p>Ad content</p>
</div>
<footer>
<p>© 2026</p>
</footer>
</body>
</html>
Notes:
- Do not invent ARIA roles (e.g.,
sidebar,hero,footer-nav). Use defined roles likecomplementary,navigation,banner,contentinfo, andmain. - Prefer native HTML elements (
aside,nav,header,footer,main) over generic containers with roles. - Label multiple complementary landmarks to make them distinguishable in screen reader landmark lists.
A span element cannot have role="text" because text is not a valid WAI-ARIA role.
The role attribute accepts only values defined in the WAI-ARIA specification. Common valid roles include button, alert, status, img, presentation, and many others, but text is not among them.
The non-standard role="text" was sometimes used as a workaround to prevent screen readers like VoiceOver from splitting inline elements into separate announcements. For example, when a span wraps mixed text and inline elements, some screen readers read each child element as a separate item. Using role="text" forced them to treat the content as a single text run. However, this was never part of the ARIA spec and causes a validation error.
If the goal is to group inline content so screen readers announce it as one continuous phrase, use role="group" with an aria-label that provides the full text. Alternatively, if the span has no semantic purpose, remove the role attribute entirely.
HTML examples
Invalid usage
<p>
<spanrole="text">
Sale price: <strong>$49.99</strong>
</span>
</p>
Valid alternatives
Remove the role if it serves no accessibility purpose:
<p>
<span>
Sale price: <strong>$49.99</strong>
</span>
</p>
Or use role="group" with aria-label to ensure screen readers announce the content as a single phrase:
<p>
<spanrole="group"aria-label="Sale price: $49.99">
Sale price: <strong>$49.99</strong>
</span>
</p>
The textbox ARIA role identifies an element that allows free-form text input. While it can technically be applied to elements using contenteditable, it should not be placed on elements that already carry strong semantic meaning, such as <li>. A list item is expected to be a child of <ul>, <ol>, or <menu>, and its implicit listitem role communicates its purpose within a list structure to assistive technologies. Assigning role="textbox" to an <li> overrides this semantic, confusing screen readers and other assistive tools about whether the element is a list item or a text input field.
This is problematic for several reasons:
- Accessibility: Screen readers rely on roles to convey the purpose of elements to users. An
<li>withrole="textbox"sends mixed signals — it exists within a list structure but announces itself as a text input. - Standards compliance: The ARIA in HTML specification restricts which roles can be applied to specific elements. The
lielement does not allow thetextboxrole, which is why the W3C validator flags this as an error. - Browser behavior: Browsers may handle the conflicting semantics unpredictably, leading to inconsistent experiences across different user agents.
The best approach is to use native HTML form elements whenever possible. The <input type="text"> element handles single-line text input, and the <textarea> element handles multi-line input. These native elements come with built-in keyboard support, focus management, and form submission behavior — none of which you get for free with a role="textbox" on a non-form element.
If you genuinely need an editable area inside a list and cannot use native form elements, nest a <div> or <span> with role="textbox" inside the <li> rather than placing the role on the <li> itself.
Examples
❌ Incorrect: role="textbox" on an li element
<ul>
<lirole="textbox"contenteditable="true">Edit this item</li>
<lirole="textbox"contenteditable="true">Edit this item too</li>
</ul>
This triggers the validator error because textbox is not a valid role for <li>.
✅ Fix: Use native form elements
The simplest and most robust fix is to use standard form controls:
<ul>
<li>
<labelfor="item1">Item 1:</label>
<inputtype="text"id="item1"value="Edit this item">
</li>
<li>
<labelfor="item2">Item 2:</label>
<inputtype="text"id="item2"value="Edit this item too">
</li>
</ul>
For multi-line input, use <textarea>:
<ul>
<li>
<labelfor="note1">Note:</label>
<textareaid="note1">Edit this content</textarea>
</li>
</ul>
✅ Fix: Nest a div with role="textbox" inside the li
If you need a contenteditable area and cannot use native form elements, place the textbox role on a nested element:
<ul>
<li>
<divid="label1">Item 1:</div>
<div
role="textbox"
contenteditable="true"
aria-labelledby="label1"
aria-placeholder="Enter text">
Edit this item
</div>
</li>
</ul>
This preserves the <li> element's implicit listitem role while correctly assigning the textbox role to a semantically neutral <div>.
✅ Fix: Remove the list structure entirely
If the items aren't truly a list, consider dropping the <ul>/<li> structure altogether:
<divid="zipLabel">Enter your five-digit zipcode</div>
<div
role="textbox"
contenteditable="true"
aria-placeholder="5-digit zipcode"
aria-labelledby="zipLabel">
</div>
In every case, prefer native <input> and <textarea> elements over role="textbox" with contenteditable. Native elements provide accessible behavior by default, including keyboard interaction, form validation, and proper focus management, without requiring additional ARIA attributes or JavaScript.
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
<divaria-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
<divaria-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
<divaria-hidden="true">
This content is hidden from assistive tech
</div>
Correct — simple false value
<divaria-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:
<divid="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) -->
<divaria-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
undefinedornullas 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><ahref="/step-1"aria-current="">Step 1</a></li>
<li><ahref="/step-2">Step 2</a></li>
</ol>
</nav>
❌ Invalid: custom keyword
Values like "active" or "yes" are not recognized:
<nav>
<ul>
<li><ahref="/"aria-current="active">Home</a></li>
<li><ahref="/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><ahref="/"aria-current="page">Home</a></li>
<li><ahref="/about">About</a></li>
</ul>
</nav>
✅ Fixed: using step for a multi-step process
<ol>
<li><ahref="/checkout/cart">Cart</a></li>
<li><ahref="/checkout/shipping"aria-current="step">Shipping</a></li>
<li><ahref="/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><ahref="/item-1"aria-current="true">Item 1</a></li>
<li><ahref="/item-2">Item 2</a></li>
<li><ahref="/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 -->
<ahref="/about"aria-current="false">About</a>
<!-- Preferred: just omit it -->
<ahref="/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 -->
<ahref="/"aria-current="page">Home</a>
<!-- What it should render for non-current pages (no attribute at all) -->
<ahref="/about">About</a>
The aria-expanded attribute only accepts the values "true", "false", or "undefined", and it belongs on interactive elements, not on generic div elements.
The aria-expanded attribute indicates whether a grouping element controlled by the current element is expanded or collapsed. Valid values are "true" (the controlled element is expanded), "false" (the controlled element is collapsed), and "undefined" (the element does not own or control a groupable element). Any other value, such as "yes", "no", "0", "1", or an empty string, is invalid.
Beyond the attribute value itself, aria-expanded is meant for interactive elements like button, a (with href), or elements with an appropriate ARIA role such as role="button". A plain div has no implicit interactivity, so placing aria-expanded on it without an interactive role triggers a validation warning.
To fix this, use a valid value and place the attribute on an appropriate element. If you must use a div, give it an interactive role and make it keyboard accessible with tabindex.
Examples
Invalid usage
<divaria-expanded="yes">
<p>Panel content</p>
</div>
Two problems here: the value "yes" is not valid, and a plain div is not an interactive element.
Valid usage
<buttonaria-expanded="false"aria-controls="panel1">
Toggle panel
</button>
<divid="panel1"hidden>
<p>Panel content</p>
</div>
The aria-expanded="false" attribute sits on a button, which is interactive by default, and the value is one of the allowed strings. When the panel opens, update the attribute to "true" and remove the hidden attribute via JavaScript.
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 -->
<inputtype="text"aria-required="">
<!-- "required" is not valid -->
<inputtype="email"aria-required="required">
✅ Correct usage with aria-required
<divid="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
<divid="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:
<labelfor="email">Email Address *</label>
<inputtype="email"id="email"name="email"required>
<labelfor="country">Country *</label>
<selectid="country"name="country"required>
<optionvalue="">Select a country</option>
<optionvalue="us">United States</option>
<optionvalue="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
disabledattribute 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:
<inputtype="text"disabled="yes">
<inputtype="text"disabled="true">
<inputtype="text"disabled="false">
<inputtype="text"disabled="1">
<buttondisabled="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:
<inputtype="text"disabled>
<inputtype="text"disabled="">
<inputtype="text"disabled="disabled">
To have an enabled input, simply omit the attribute:
<inputtype="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>
<inputtype="text"id="username">
<buttontype="button"id="toggle">Toggle</button>
<script>
document.getElementById("toggle").addEventListener("click",function(){
varinput=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 -->
<inputtype="checkbox"checked="true">
<inputtype="email"required="required_field">
<!-- Correct -->
<inputtype="checkbox"checked>
<inputtype="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:
<inputtype="text"required="true">
<inputtype="email"required="false">
<inputtype="text"required="yes">
<inputtype="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) -->
<inputtype="text"required>
<!-- Also valid: empty string -->
<inputtype="text"required="">
<!-- Also valid: canonical name as value -->
<inputtype="text"required="required">
Correct: Making a field optional
To make a field optional, remove the attribute entirely:
<inputtype="text">
Full form example
<formaction="/submit"method="post">
<labelfor="name">Name (required):</label>
<inputtype="text"id="name"name="name"required>
<labelfor="notes">Notes (optional):</label>
<inputtype="text"id="notes"name="notes">
<buttontype="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.
Multiple h1 elements on a page can confuse screen readers and other assistive tools, which treat every h1 as the top-level heading.
HTML headings (h1 through h6) form an outline of your document. The h1 element represents the highest-level heading, and most accessibility guidelines recommend using only one h1 per page. When screen readers encounter multiple h1 elements, they may present them all as equally important top-level sections, making it harder for users to understand the page structure.
Instead of using multiple h1 elements, use a proper heading hierarchy. Start with a single h1 for the main topic of the page, then use h2 for major sections, h3 for subsections, and so on. This creates a clear, navigable document outline.
The W3C warning also mentions a headingoffset attribute, which is a proposed feature for <section> elements that would allow automatic heading level adjustment. However, this attribute is not yet implemented in any browser, so you should not rely on it.
Example with the issue
<body>
<h1>My Website</h1>
<section>
<h1>About Us</h1>
<p>Some content here.</p>
</section>
<section>
<h1>Contact</h1>
<p>More content here.</p>
</section>
</body>
Example with proper heading hierarchy
<body>
<h1>My Website</h1>
<section>
<h2>About Us</h2>
<p>Some content here.</p>
</section>
<section>
<h2>Contact</h2>
<p>More content here.</p>
</section>
</body>
Keep one h1 per page and nest subsequent headings using h2 through h6 to reflect the logical structure of your content. This approach is well-supported across all browsers and assistive technologies today.
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
roleattribute 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 ofnavigation,<button>has an implicit role ofbutton, and<header>has an implicit role ofbanner. 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.
<divaria-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.
<divrole="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.
<buttonaria-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.
<spanaria-label="Close dialog">X</span>
Fixed: Using a semantic element or adding a role
<buttonaria-label="Close dialog">X</button>
Or, if you need to use a <span>:
<spanrole="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" -->
<navaria-label="Main navigation">
<ul>
<li><ahref="/">Home</a></li>
<li><ahref="/about">About</a></li>
</ul>
</nav>
<!-- <details> supports aria-expanded implicitly -->
<detailsaria-describedby="help-text">
<summary>More information</summary>
<pid="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-checked attribute is not allowed on a label element when that label is associated with a form control like a checkbox or radio button.
When a label is associated with a labelable element (via the for attribute or by nesting), the label acts as an accessible name provider — it doesn't represent the control's state itself. The aria-checked attribute is meant for elements that act as a checkbox or radio button, such as elements with role="checkbox" or role="switch", not for labels that merely describe one.
Adding aria-checked to a label creates conflicting semantics. Assistive technologies already read the checked state from the associated input element, so duplicating or overriding that state on the label causes confusion.
If you need a custom toggle or checkbox, apply aria-checked to the element that has the interactive role, not to the label.
Example with the issue
<labelfor="notifications"aria-checked="true">Enable notifications</label>
<inputtype="checkbox"id="notifications"checked>
Fixed example using a native checkbox
<labelfor="notifications">Enable notifications</label>
<inputtype="checkbox"id="notifications"checked>
The native <input type="checkbox"> already communicates its checked state to assistive technologies — no aria-checked is needed anywhere.
Fixed example using a custom toggle
If you're building a custom control without a native checkbox, apply aria-checked to the element with the appropriate role:
<spanid="toggle-label">Enable notifications</span>
<spanrole="switch"tabindex="0"aria-checked="true"aria-labelledby="toggle-label"></span>
Here, aria-checked is correctly placed on the element with role="switch", which is the interactive control. The label is a separate <span> referenced via aria-labelledby, keeping roles and states cleanly separated.
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
idor thearia-controlsvalue. - Dynamically generated content where the controlled element hasn't been rendered yet or has been removed from the DOM.
- Copy-paste errors where
aria-controlswas copied from another component but the correspondingidwas 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
idthataria-controlsreferences. - Check for typos — ID matching is case-sensitive, so
mainPanelandmainpanelare 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-controlsprogrammatically when the target becomes available. - If the controlled element is genuinely absent (e.g., conditionally rendered), remove the
aria-controlsattribute until the target element exists.
Examples
Incorrect: aria-controls references a non-existent ID
<buttonaria-controls="info-panel"aria-expanded="false">
Toggle Info
</button>
<divid="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
<buttonaria-controls="info-panel"aria-expanded="false">
Toggle Info
</button>
<divid="info-panel">
<p>Here is some additional information.</p>
</div>
Correct: Tab and tab panel relationship
<divrole="tablist">
<buttonrole="tab"aria-controls="tab1-panel"aria-selected="true">
Overview
</button>
<buttonrole="tab"aria-controls="tab2-panel"aria-selected="false">
Details
</button>
</div>
<divid="tab1-panel"role="tabpanel">
<p>Overview content goes here.</p>
</div>
<divid="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
<divrole="scrollbar"aria-controls="main-content"aria-valuenow="0"aria-valuemin="0"aria-valuemax="100"aria-orientation="vertical"></div>
<divid="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.
<buttonaria-controls="section-a section-b">
Expand All Sections
</button>
<divid="section-a">
<p>Section A content.</p>
</div>
<divid="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
idvalue — thearia-describedbyvalue doesn't match the target element'sidexactly (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
idwas changed or the element was removed, but thearia-describedbyreference 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-describedbyagainst theidof the target element. Remember thatidmatching is case-sensitive —helpTextandhelptextare 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-describedbyattribute entirely rather than leaving a broken reference. - Verify all IDs in a multi-value list. If
aria-describedbycontains 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:
<labelfor="password">Password</label>
<inputtype="password"id="password"aria-describedby="password-help">
Fixed by adding the referenced element
Adding an element with id="password-help" resolves the issue:
<labelfor="password">Password</label>
<inputtype="password"id="password"aria-describedby="password-help">
<pid="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:
<inputtype="text"id="email"aria-describedby="emailHelp">
<smallid="emailhelp">We'll never share your email.</small>
The fix is to make the id values match exactly:
<inputtype="text"id="email"aria-describedby="email-help">
<smallid="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 -->
<inputtype="text"id="username"aria-describedby="format-hint length-hint">
<spanid="format-hint">Letters and numbers only.</span>
Fix it by adding the missing element:
<inputtype="text"id="username"aria-describedby="format-hint length-hint">
<spanid="format-hint">Letters and numbers only.</span>
<spanid="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:
<inputtype="text"id="search">
The aria-expanded attribute is redundant on a summary element when it is the direct child of a details element, because the browser already communicates the expanded/collapsed state through the native open attribute on details.
The summary element, when used as the first child of a details element, acts as the built-in toggle control. Assistive technologies already understand this relationship and automatically convey whether the disclosure widget is open or closed based on the details element's open attribute. Adding aria-expanded to summary in this context creates duplicate semantics, which can confuse screen readers by announcing the state twice.
If you're using JavaScript to toggle aria-expanded manually, you can safely remove it and rely on the native behavior instead. The details/summary pattern is one of the best examples of built-in accessibility that requires no extra ARIA attributes.
Incorrect Example
<details>
<summaryaria-expanded="false">More information</summary>
<p>Here are the additional details.</p>
</details>
Correct Example
<details>
<summary>More information</summary>
<p>Here are the additional details.</p>
</details>
If you need the section to be open by default, use the open attribute on the details element:
<detailsopen>
<summary>More information</summary>
<p>Here are the additional details.</p>
</details>
The aria-label attribute is not allowed on a <label> element when that <label> contains a labelable element (such as <input>, <select>, <textarea>, or <button>).
The <label> element already provides an accessible name for its associated form control through its text content. When a <label> wraps a labelable element, adding aria-label to the <label> creates a conflict: the <label> has one accessible name (from aria-label) while the form control inside it derives its accessible name from the <label>'s text content. Assistive technologies may handle this inconsistency unpredictably.
The HTML spec restricts aria-label on <label> elements that are ancestors of labelable elements. A "labelable element" is any element that can be associated with a <label>, including <input> (except type="hidden"), <select>, <textarea>, <button>, <meter>, <output>, and <progress>.
If the <label> needs visible text, just use the text content of the <label> directly. If you need to provide an accessible name that differs from the visible text, place aria-label on the form control itself instead of on the <label>.
Examples
Invalid: aria-label on a label that wraps an input
<labelaria-label="Enter your email address">
<inputtype="email"name="email">
</label>
Fixed: move aria-label to the input
If the visible label text is sufficient, remove aria-label entirely:
<label>
<inputtype="email"name="email">
</label>
If you need a more descriptive accessible name for the input, place aria-label on the input:
<label>
<inputtype="email"name="email"aria-label="Enter your email address">
</label>
The aria-label attribute is not allowed on a label element that is associated with a form control through the for attribute.
A <label> element already provides an accessible name for the form control it's associated with. Adding aria-label to the <label> itself creates a conflict: the aria-label would attempt to override the label's own accessible name, but the label's visible text is what gets passed to the associated form control. This redundancy is not only unnecessary but explicitly prohibited by the HTML specification.
The <label> element's purpose is to be the accessible label for another element. If you want the form control to have an accessible name, simply put that text inside the <label> element as visible content. If you need to provide a different accessible name directly to the form control, place the aria-label on the input element instead.
Incorrect Example
<labelfor="input_email"id="label_input_email"aria-label="Email">
</label>
<inputtype="email"id="input_email">
Correct Example
The simplest fix is to remove the aria-label from the <label>, since the label's text content already serves as the accessible name for the input:
<labelfor="input_email"id="label_input_email">
</label>
<inputtype="email"id="input_email">
If you need the accessible name to differ from the visible label text, place aria-label on the input instead:
<labelfor="input_email"id="label_input_email">
</label>
<inputtype="email"id="input_email"aria-label="Your email address">
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:
<articlerole="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:
<divrole="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:
<sectionrole="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:
<headerrole="banner">
<imgsrc="logo.svg"alt="My Company">
<nav>
<ahref="/">Home</a>
<ahref="/about">About</a>
</nav>
</header>
Correct: let <header> use its implicit role
Simply remove the role="banner" attribute:
<header>
<imgsrc="logo.svg"alt="My Company">
<nav>
<ahref="/">Home</a>
<ahref="/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:
<divrole="banner">
<imgsrc="logo.svg"alt="My Company">
<nav>
<ahref="/">Home</a>
<ahref="/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 <timedatetime="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
<buttonrole="button">Buy now</button>
<buttontype="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>
<buttontype="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
<divrole="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
<buttononclick="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 -->
<ahref="/about"role="link">About</a>
<navrole="navigation">...</nav>
<h1role="heading">Title</h1>
<inputtype="checkbox"role="checkbox">
<!-- ✅ Clean -->
<ahref="/about">About</a>
<nav>...</nav>
<h1>Title</h1>
<inputtype="checkbox">
Validate at scale.
Ship accessible websites, faster.
Automated HTML & accessibility validation for large sites. Check thousands of pages against WCAG guidelines and W3C standards in minutes, not days.
Pro Trial
Full Pro access. Cancel anytime.
Start Pro Trial →Join teams across 40+ countries