HTML Guides for role
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
An element with role="tab" requires a corresponding element with role="tabpanel" in the same document. Without this pairing, assistive technologies cannot associate the tab with the content it controls.
The WAI-ARIA specification defines a tab interface as a set of layered content areas, where only one panel is visible at a time. Each role="tab" element must reference a role="tabpanel" element through the aria-controls attribute, and each role="tabpanel" should reference its tab back using aria-labelledby.
The tabs themselves must be wrapped in a container with role="tablist". The selected tab gets aria-selected="true", while inactive tabs get aria-selected="false". Each tabpanel that is not currently visible should be hidden with the hidden attribute or equivalent CSS.
The W3C validator flags this error when it finds a role="tab" element but no matching role="tabpanel" exists in the document. This can happen when the tab panels are missing entirely, or when they exist but lack the role="tabpanel" attribute.
Invalid example
<div role="tablist">
<button role="tab" aria-selected="true" aria-controls="panel-1">Tab 1</button>
<button role="tab" aria-selected="false" aria-controls="panel-2">Tab 2</button>
</div>
<div id="panel-1">Content for tab 1</div>
<div id="panel-2" hidden>Content for tab 2</div>
The two div elements exist but have no role="tabpanel", so the validator reports the error.
Valid example
<div role="tablist">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">Tab 1</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2">Tab 2</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
Content for tab 1
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
Content for tab 2
</div>
Each role="tab" now has a corresponding role="tabpanel". The aria-controls on each tab points to the id of its panel, and aria-labelledby on each panel points back to the id of its tab.
An aria-label attribute on an <a> element is only valid when the link has an accessible role that supports naming — which means the <a> must have an href attribute or an explicit role that accepts a label.
When an <a> element lacks an href attribute, it has the implicit role of generic. The generic role is in the list of roles that do not support naming, so applying aria-label to it is invalid. This is because a generic element has no semantic meaning, and screen readers wouldn't know how to announce the label in a meaningful way.
The most common cause of this error is using <a> as a placeholder or JavaScript-only trigger without an href. An <a> with an href has the implicit role of link, which does support aria-label, so the error won't appear.
You have a few ways to fix this:
- Add an
hrefto make it a proper link (most common fix). - Add an explicit role that supports naming, such as
role="button", if the element acts as a button. - Use a
<button>instead if the element triggers an action rather than navigation. - Remove
aria-labelif it's not needed, and use visible text content instead.
HTML Examples
❌ Invalid: aria-label on an <a> without href
<a aria-label="Close menu" onclick="closeMenu()">✕</a>
The <a> has no href, so its implicit role is generic, which does not support naming.
✅ Fix option 1: Add an href
<a href="/close" aria-label="Close menu">✕</a>
✅ Fix option 2: Use a <button> instead
<button aria-label="Close menu" onclick="closeMenu()">✕</button>
✅ Fix option 3: Add an explicit role that supports naming
<a role="button" tabindex="0" aria-label="Close menu" onclick="closeMenu()">✕</a>
Using a <button> (option 2) is generally the best choice for interactive elements that perform actions rather than navigate to a URL.
A div element without an explicit role resolves to the generic role, which does not support naming — so adding aria-label to a plain div is invalid.
The aria-label attribute provides an accessible name for an element, but not every element is allowed to have one. The ARIA specification defines certain roles as "naming prohibited," meaning assistive technologies will ignore any accessible name applied to them. The generic role is one of these, and since a div without an explicit role attribute defaults to generic, the aria-label is effectively meaningless.
To fix this, you have two main options: assign an appropriate ARIA role to the div so it becomes a nameable landmark or widget, or switch to a semantic HTML element that already carries a valid role. Common roles that support naming include region, group, navigation, alert, and many others.
If the div is truly just a generic wrapper with no semantic meaning, consider whether aria-label is even needed. Perhaps the label belongs on a child element instead, or the content is already self-describing.
HTML Examples
❌ Invalid: aria-label on a plain div
<div aria-label="User profile section">
<p>Welcome, Jane!</p>
</div>
✅ Fix: Add an appropriate role
<div role="region" aria-label="User profile section">
<p>Welcome, Jane!</p>
</div>
✅ Fix: Use a semantic element instead
<section aria-label="User profile section">
<p>Welcome, Jane!</p>
</section>
The aria-label attribute cannot be used on an <i> element with its default implicit role (generic), because generic elements are not allowed to have accessible names.
The <i> element has an implicit ARIA role of generic, which is one of the roles explicitly prohibited from carrying an aria-label. This restriction exists because screen readers and other assistive technologies ignore accessible names on generic containers — so adding aria-label to a plain <i> element would silently fail to convey any meaning to users who rely on assistive technology.
This issue commonly appears when icon fonts (like Font Awesome) use <i> elements as decorative icons. If the icon is purely decorative, you should hide it from assistive technology with aria-hidden="true" and place the accessible label on a parent or sibling element instead. If the icon conveys meaning on its own, you can assign an appropriate role like role="img" so the aria-label is actually announced.
HTML Examples
❌ Invalid: aria-label on a plain <i> element
<button>
<i class="icon-search" aria-label="Search"></i>
</button>
✅ Fix 1: Decorative icon — hide it, label the parent
<button aria-label="Search">
<i class="icon-search" aria-hidden="true"></i>
</button>
✅ Fix 2: Meaningful icon — assign role="img"
<button>
<i class="icon-search" role="img" aria-label="Search"></i>
</button>
The aria-label attribute cannot be used on a custom element like <menu-item> when it has no explicit role attribute, because it defaults to the generic role, which is in the list of roles that prohibit aria-label.
Custom elements without an explicit role are treated as having the generic role (equivalent to a <span> or <div> in terms of semantics). The WAI-ARIA specification prohibits aria-label on several roles, including generic, because naming these elements creates a confusing experience for assistive technology users — a generic container with a label doesn't convey any meaningful purpose.
To fix this, you need to assign a meaningful role to the <menu-item> element that supports accessible naming. Common choices include role="menuitem", role="link", or role="button", depending on what the element actually does. Since this appears to represent a menu item that navigates to a page, role="menuitem" is likely the most appropriate.
HTML Examples
❌ Invalid: aria-label on an element with implicit generic role
<menu-item
submenu-href="/page"
label="some label"
submenu-title="some submenu title"
aria-label="some aria label">
</menu-item>
✅ Valid: adding an explicit role that supports aria-label
<menu-item
role="menuitem"
submenu-href="/page"
label="some label"
submenu-title="some submenu title"
aria-label="some aria label">
</menu-item>
If the aria-label isn't actually needed (for example, if assistive technology already receives the label through other means in your component), another valid fix is to simply remove aria-label entirely.
A span element has an implicit ARIA role of generic, and the aria-label attribute is not allowed on elements with that role.
The span element is a generic inline container with no semantic meaning. Its default ARIA role is generic, which is one of several roles that prohibit naming via aria-label or aria-labelledby. This restriction exists because screen readers are not expected to announce names for generic containers — adding aria-label to them creates an inconsistent and confusing experience for assistive technology users.
To fix this, you have two main options:
- Assign an explicit role to the
spanthat supports naming, such asrole="img",role="group",role="status", or any other role that allowsaria-label. - Use a different element that already has a semantic role supporting
aria-label, such as abutton,a,section, ornav.
If the span is purely decorative or used for styling, consider using aria-hidden="true" instead and placing accessible text elsewhere.
HTML Examples
❌ Invalid: aria-label on a plain span
<span aria-label="Close">✕</span>
✅ Fixed: assign an appropriate role
<span role="img" aria-label="Close">✕</span>
✅ Fixed: use a semantic element instead
<button aria-label="Close">✕</button>
✅ Fixed: hide the decorative span and provide text another way
<button>
<span aria-hidden="true">✕</span>
<span class="visually-hidden">Close</span>
</button>
The <time> element does not support the aria-label attribute when it has no explicit role or when it carries certain generic roles.
The <time> element has an implicit ARIA role of time, but this role is not listed among those that allow aria-label. According to the ARIA in HTML specification, aria-label is only permitted on elements with roles that support naming from author — and the default role of <time> (as well as roles like generic, presentation, paragraph, and others listed in the error) does not qualify.
In practice, the <time> element already conveys its meaning through its visible text content and the machine-readable datetime attribute. Screen readers use the visible text to announce the date, so aria-label is typically unnecessary.
To fix this, simply remove the aria-label attribute and ensure the visible text content is descriptive enough. If you need to provide a more accessible reading of the date, you can adjust the visible text itself or wrap the element with a <span> that has an appropriate role.
Also note the original code has a missing space before datetime — the attribute must be separated from class="Tag" by a space.
HTML Examples
❌ Invalid: aria-label on <time>
<time aria-label="Apr 2." class="Tag" datetime="2026-04-02">
Apr 2.
</time>
✅ Fixed: Remove aria-label and use clear visible text
<time class="Tag" datetime="2026-04-02">
April 2, 2026
</time>
If you truly need aria-label, you can assign an explicit role that supports naming, such as role="text", though this is rarely necessary:
<time role="text" aria-label="April 2nd, 2026" class="Tag" datetime="2026-04-02">
Apr 2.
</time>
A div element without an explicit role (or with role="generic") cannot have the aria-labelledby attribute because generic containers have no semantic meaning that benefits from a label.
The div element maps to the generic ARIA role by default. Generic elements are purely structural — they don't represent anything meaningful to assistive technologies. Labeling something that has no semantic purpose creates a confusing experience for screen reader users, since the label points to an element that doesn't convey a clear role.
The aria-labelledby attribute is designed for interactive or landmark elements — things like dialog, region, navigation, form, or group — where a label helps users understand the purpose of that section.
To fix this, you have two options: assign a meaningful ARIA role to the div, or use a more semantic HTML element that naturally supports labeling.
HTML Examples
❌ Invalid: aria-labelledby on a plain div
<h2 id="section-title">User Settings</h2>
<div aria-labelledby="section-title">
<p>Manage your account preferences here.</p>
</div>
✅ Fixed: Add a meaningful role to the div
<h2 id="section-title">User Settings</h2>
<div role="region" aria-labelledby="section-title">
<p>Manage your account preferences here.</p>
</div>
✅ Fixed: Use a semantic element instead
<h2 id="section-title">User Settings</h2>
<section aria-labelledby="section-title">
<p>Manage your account preferences here.</p>
</section>
Using a section element or adding role="region" tells assistive technologies that this is a distinct, meaningful area of the page — making the label useful and the markup valid.
A span element has an implicit ARIA role of generic, and the aria-labelledby attribute is not allowed on elements with that role.
The span element is a generic inline container with no semantic meaning. Its default ARIA role is generic, and the ARIA specification explicitly prohibits naming generic elements with aria-labelledby (or aria-label). This restriction exists because accessible names on generic containers create confusing experiences for assistive technology users — screen readers wouldn't know what kind of thing is being labeled.
To fix this, you have two main options:
- Add a meaningful
roleto thespanthat supportsaria-labelledby, such asrole="group",role="region", or any other role that accepts a label. - Use a more semantic element that already has an appropriate role, like a
section,nav, ordivwith an explicit role.
If the span doesn't truly need a label, simply remove the aria-labelledby attribute.
HTML Examples
❌ Invalid: aria-labelledby on a plain span
<span id="label">Settings</span>
<span aria-labelledby="label">
<input type="checkbox" id="opt1">
<label for="opt1">Enable notifications</label>
</span>
✅ Fix: Add an appropriate role
<span id="label">Settings</span>
<span role="group" aria-labelledby="label">
<input type="checkbox" id="opt1">
<label for="opt1">Enable notifications</label>
</span>
✅ Fix: Use a semantic element instead
<span id="label">Settings</span>
<fieldset aria-labelledby="label">
<input type="checkbox" id="opt1">
<label for="opt1">Enable notifications</label>
</fieldset>
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 Enter or Space — all without any explicit ARIA role.
Many HTML5 semantic elements come with built-in (implicit) ARIA roles defined in the WAI-ARIA specification. The <aside> element is one of these — it natively maps to the complementary role, which tells assistive technologies that the content is related to the main content but can stand on its own. When you explicitly add role="complementary" to an <aside>, you're stating something the browser already knows, which triggers this W3C validator warning.
While this redundancy won't break anything for end users, it creates unnecessary noise in your code and can signal a misunderstanding of how semantic HTML works. Keeping markup free of redundant ARIA attributes follows 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." Clean, semantic HTML is easier to maintain and less prone to errors if ARIA roles are accidentally changed or conflict with the native semantics in the future.
This same principle applies to several other HTML elements, such as <nav> (implicit role navigation), <main> (implicit role main), <header> (implicit role banner when not nested), and <footer> (implicit role contentinfo when not nested).
Examples
❌ Redundant role on <aside>
The role="complementary" attribute is unnecessary because <aside> already implies it:
<aside role="complementary">
<h2>Related Articles</h2>
<ul>
<li><a href="/article-1">Understanding ARIA roles</a></li>
<li><a href="/article-2">Semantic HTML best practices</a></li>
</ul>
</aside>
✅ Using <aside> without the redundant role
Simply remove the role attribute:
<aside>
<h2>Related Articles</h2>
<ul>
<li><a href="/article-1">Understanding ARIA roles</a></li>
<li><a href="/article-2">Semantic HTML best practices</a></li>
</ul>
</aside>
✅ When an explicit role is appropriate
If you need to give the <aside> element a different role than its default, an explicit role attribute is valid and useful. For example, you might use <aside> for structural reasons but assign it a different ARIA role:
<aside role="note">
<p>This feature is only available in version 3.0 and later.</p>
</aside>
✅ Labeling multiple <aside> elements
If your page has multiple <aside> elements, you don't need to add role="complementary" to distinguish them. Instead, use aria-label or aria-labelledby to give each a unique accessible name:
<aside aria-label="Related articles">
<h2>Related Articles</h2>
<ul>
<li><a href="/article-1">Understanding ARIA roles</a></li>
</ul>
</aside>
<aside aria-labelledby="ad-heading">
<h2 id="ad-heading">Sponsored Content</h2>
<p>Check out our latest product.</p>
</aside>
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 WAI-ARIA specification defines strict rules about which elements can be children of interactive roles. An element with role="button" is treated as an interactive control, and the <a> element (when it has an href) is also an interactive control. Nesting one interactive element inside another creates what's known as an interactive content nesting violation. Screen readers and other assistive technologies cannot reliably determine user intent when they encounter this pattern — should they announce a button, a link, or both? The result is unpredictable behavior that can make your content inaccessible.
This issue commonly appears when developers wrap links inside styled containers that have been given role="button", or when using component libraries that apply button semantics to wrapper elements containing anchor tags.
Beyond accessibility, browsers themselves handle nested interactive elements inconsistently. Some may ignore the outer interactive role, while others may prevent the inner link from functioning correctly. This makes the pattern unreliable even for users who don't rely on assistive technologies.
How to fix it
There are several approaches depending on your intent:
- If the element should navigate to a URL, use an
<a>element and style it to look like a button. Remove therole="button"from the parent or eliminate the parent wrapper entirely. - If the element should perform an action (not navigation), use a
<button>element or an element withrole="button", and handle the action with JavaScript. Remove the nested<a>tag. - If you need both a button container and a link, flatten the structure so they are siblings rather than nested.
When using role="button" on a non-button element, remember that you must also handle keyboard interaction (Enter and Space key presses) and include tabindex="0" to make it focusable.
Examples
❌ Incorrect: link nested inside a button role
<div role="button">
<a href="/dashboard">Go to Dashboard</a>
</div>
This triggers the validation error because the <a> is a descendant of an element with role="button".
✅ Fix 1: Use a styled link instead
If the intent is navigation, remove the button role and let the <a> element do the job. Style it to look like a button with CSS.
<a href="/dashboard" class="btn">Go to Dashboard</a>
This is the simplest and most semantically correct fix when the purpose is to navigate the user to another page.
✅ Fix 2: Use a real button for actions
If the intent is to trigger an action (not navigate), replace the entire structure with a <button> element.
<button type="button" class="btn" onclick="navigateToDashboard()">
Go to Dashboard
</button>
✅ Fix 3: Use a div with role="button" without nested interactive elements
If you need a custom button using role="button", make sure it contains no interactive descendants.
<div role="button" tabindex="0">
Go to Dashboard
</div>
When using this approach, you must also add keyboard event handlers for Enter and Space to match native button behavior.
❌ Incorrect: link inside a button element
The same principle applies to native <button> elements, not just elements with role="button":
<button>
<a href="/settings">Settings</a>
</button>
✅ Fixed: choose one interactive element
<a href="/settings" class="btn">Settings</a>
❌ Incorrect: deeply nested link
The error applies to any level of nesting, not just direct children:
<div role="button">
<span class="icon-wrapper">
<a href="/help">Help</a>
</span>
</div>
✅ Fixed: flatten the structure
<a href="/help" class="btn">
<span class="icon-wrapper">Help</span>
</a>
As a rule of thumb, every interactive element in your page should have a single, clear role. If something looks like a button but navigates to a URL, make it an <a> styled as a button. If it performs an in-page action, make it a <button>. Keeping these roles distinct ensures your HTML is valid, accessible, and behaves consistently across browsers and assistive technologies.
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 givenrole="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.
A <button> element must not be placed inside any element that carries role="img", because that role tells assistive technologies to treat the element and everything inside it as a single, flat image.
When you set role="img" on a container, screen readers stop exposing its children as separate, operable controls. The whole subtree collapses into one graphic with a single accessible name. A <button> nested inside disappears from that flattened view: a screen reader user cannot reach or activate it, even though the button still renders and responds to a mouse. The checker flags this because the markup promises an image but hides an interactive control inside it.
This usually happens when role="img" is added to a wrapper that groups an icon or illustration together with a real control, such as a decorative card that also holds a button.
The fix is to decide what the element actually is. If it is an image, keep role="img" and move the button outside it. If it needs a button, remove role="img" from the ancestor and describe any decorative graphics with alt text or aria-label on the image itself.
Invalid example
<div role="img" aria-label="Play the intro video">
<img src="thumbnail.jpg" alt="">
<button type="button">Play</button>
</div>
Valid example
<button type="button" aria-label="Play the intro video">
<img src="thumbnail.jpg" alt="">
</button>
Heading elements <h1> through <h6> must not appear inside an element with role="button". ARIA exposes a button as a single, flat control and treats its contents as presentational, so a heading placed inside it is no longer announced as a heading. Move the heading outside the control, or use a native <button> for the part that is actually clickable.
The WAI-ARIA specification marks button as a role whose children are presentational. When the browser maps a role="button" element into the accessibility tree, it drops the roles of everything inside it, so the heading level is lost. The W3C validator reports the conflict instead of letting the heading semantics disappear silently.
This usually happens when a clickable card or toggle is built from a <div role="button"> that wraps a heading. Headings are navigation points that screen reader users jump between, and they form the document outline. Burying one inside a button removes it from heading navigation and flattens the outline.
Keep the heading as a sibling of the control rather than a descendant. When the whole region needs to be activatable, use a native <button> (or a link) for the action and leave the heading outside it.
Invalid example
<div role="button">
<h6>Save changes</h6>
</div>
Valid example
<h6>Settings</h6>
<button type="button">Save changes</button>
Many HTML elements come with built-in (implicit) ARIA roles that browsers and assistive technologies already recognize. The <form> element natively maps to the form ARIA role, meaning screen readers and other tools already understand it as a form landmark without any extra attributes. When you explicitly add role="form" to a <form> element, you're telling the browser something it already knows.
This redundancy is problematic for several reasons:
- Code clarity: Unnecessary attributes make your HTML harder to read and maintain. Other developers may wonder if the explicit role is there to override something or if it serves a special purpose.
- Misleading intent: Explicit ARIA roles are typically reserved for cases where you need to override or supplement the default semantics of an element. Using them unnecessarily can signal to future maintainers that something unusual is happening when it isn't.
- ARIA best practices: The first rule of ARIA is "do not use ARIA if you can use a native HTML element or attribute with the semantics and behavior you require." Adding redundant ARIA roles goes against this principle.
It's worth noting that the <form> element's implicit form role only exposes it as a landmark when the form has an accessible name (e.g., via aria-label or aria-labelledby). If you need your form to appear as a landmark region, provide an accessible name rather than adding a redundant role.
To fix this issue, simply remove role="form" from any <form> element. If you want the form to function as a named landmark for assistive technology users, add an accessible name instead.
Examples
❌ Incorrect: redundant role="form"
<form role="form" action="/subscribe" method="post">
<label for="email">Email:</label>
<input type="email" id="email" name="email">
<button type="submit">Subscribe</button>
</form>
This triggers the validator warning because role="form" duplicates the element's implicit role.
✅ Correct: no explicit role
<form action="/subscribe" method="post">
<label for="email">Email:</label>
<input type="email" id="email" name="email">
<button type="submit">Subscribe</button>
</form>
The <form> element already communicates its role natively. No ARIA attribute is needed.
✅ Correct: form with an accessible name for landmark navigation
<form action="/subscribe" method="post" aria-label="Newsletter subscription">
<label for="email">Email:</label>
<input type="email" id="email" name="email">
<button type="submit">Subscribe</button>
</form>
If you want the form to be discoverable as a named landmark by screen reader users, provide an aria-label or aria-labelledby attribute — not a redundant role.
Other elements with implicit roles
The same principle applies to many other HTML elements. Avoid adding redundant roles like these:
<!-- ❌ Redundant roles -->
<nav role="navigation">...</nav>
<main role="main">...</main>
<header role="banner">...</header>
<footer role="contentinfo">...</footer>
<button role="button">Click me</button>
<!-- ✅ Let native semantics do the work -->
<nav>...</nav>
<main>...</main>
<header>...</header>
<footer>...</footer>
<button>Click me</button>
Trust the native semantics of HTML elements. Only use explicit ARIA roles when you genuinely need to change or supplement an element's default behavior.
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-levelif 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"witharia-levelon 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.
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