HTML Guides for button
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 HTML specification defines strict rules about which elements can be nested inside others. The <a> element is classified as interactive content, and its content model explicitly forbids other interactive content as descendants. Elements with role="button" — whether a native <button> or any element with the ARIA role — are also interactive. Nesting one inside the other creates an ambiguous situation: should a click activate the link or the button?
This matters for several important reasons:
- Accessibility: Screen readers and assistive technologies rely on a clear, unambiguous element hierarchy. When a button is inside a link, the announced role becomes confusing — users may hear both a link and a button, or the assistive technology may only expose one of them, hiding the other’s functionality.
- Unpredictable behavior: Browsers handle nested interactive elements inconsistently. Some may split the elements apart in the DOM, while others may swallow click events. This leads to broken or unreliable behavior across different browsers.
- Standards compliance: The WHATWG HTML Living Standard and W3C HTML specification both explicitly prohibit this nesting pattern.
To fix this issue, decide what the element should actually do. If it navigates to a URL, use an <a> element. If it performs an action (like submitting a form or triggering JavaScript), use a <button>. If you need both visual styles, use CSS to style one element to look like the other.
Examples
❌ Incorrect: Element with role="button" inside an <a>
<a href="/dashboard">
<span role="button">Go to Dashboard</span>
</a>
The <span> with role="button" is interactive content nested inside the interactive <a> element.
❌ Incorrect: <button> inside an <a>
<a href="/settings">
<button>Open Settings</button>
</a>
A <button> element is inherently interactive and must not appear inside an <a>.
✅ Correct: Use a link styled as a button
If the purpose is navigation, use the <a> element and style it with CSS:
<a href="/dashboard" class="btn">Go to Dashboard</a>
✅ Correct: Use a button that navigates via JavaScript
If you need a button that also navigates, handle navigation in JavaScript:
<button type="button" onclick="location.href='/dashboard'">Go to Dashboard</button>
✅ Correct: Place elements side by side
If you truly need both a link and a button, keep them as siblings rather than nesting one inside the other:
<div class="actions">
<a href="/dashboard">Go to Dashboard</a>
<button type="button">Save Preference</button>
</div>
✅ Correct: Link styled as a button using ARIA (when appropriate)
If the element navigates but you want assistive technologies to announce it as a button, you can apply role="button" directly to the element — just don’t nest it inside an <a>:
<span role="button" tabindex="0" onclick="location.href='/dashboard'">
Go to Dashboard
</span>
Note that using role="button" on a non-interactive element like <span> requires you to also add tabindex="0" for keyboard focusability and handle keydown events for the Enter and Space keys. In most cases, a native <a> or <button> element is the better choice.
A descendant element with a tabindex attribute cannot be nested inside an element that uses role="button".
The role="button" attribute marks an element as a button for assistive technologies, but true button elements (using the button tag) should typically manage focus and tab order on their own. Adding a nested element with tabindex inside a role=”button” container can confuse keyboard navigation and accessibility tools. Each tabbable element should have a clear, non-overlapping place in the focus order.
Incorrect example (causes the error):
<div role="button">
<span tabindex="0">Click me</span>
</div>
Corrected example 1: Move tabindex to the container
<div role="button" tabindex="0">
<span>Click me</span>
</div>
Corrected example 2: Use a native button element
<button>
<span>Click me</span>
</button>
Whenever possible, use native elements like button, as they provide correct focus, keyboard, and accessibility behavior without extra attributes or roles. Only use role="button" with proper keyboard and accessibility support if absolutely necessary, and avoid tabbable descendants within such containers.
The HTML specification defines button as an interactive content element that accepts phrasing content as its children, but explicitly forbids interactive content as descendants. When you add a tabindex attribute to an element, you make it focusable and potentially interactive, which violates this content model restriction.
This rule exists for important reasons. A button element is a single interactive control — when a user presses Tab, the entire button receives focus as one unit. If elements inside the button also have tabindex, screen readers and keyboard users encounter nested focusable items within what should be a single action target. This creates confusing, unpredictable behavior: users may tab into the button’s internals without understanding the context, and assistive technologies may announce the inner elements separately, breaking the expected interaction pattern.
Browsers may also handle nested focusable elements inconsistently. Some may ignore the inner tabindex, while others may allow focus on the nested element but not properly trigger the button’s click handler, leading to broken functionality.
How to fix it
The most straightforward fix is to remove the tabindex attribute from any elements inside the button. If the inner element was given tabindex="0" to make it focusable, it doesn’t need it — the button itself is already focusable. If it was given tabindex="-1" to programmatically manage focus, reconsider whether that focus management is necessary within a button context.
If you genuinely need multiple interactive elements in the same area, restructure your markup so that the interactive elements are siblings rather than nested inside a button.
Examples
❌ Incorrect: span with tabindex inside a button
<button type="button">
<span tabindex="0">Click me</span>
</button>
The span has tabindex="0", making it a focusable descendant of the button. This violates the content model.
✅ Correct: Remove tabindex from the descendant
<button type="button">
<span>Click me</span>
</button>
The span no longer has tabindex, so the button behaves as a single focusable control.
❌ Incorrect: Multiple focusable elements inside a button
<button type="button">
<span tabindex="0" class="icon">★</span>
<span tabindex="-1" class="label">Favorite</span>
</button>
Both inner span elements have tabindex attributes, which is invalid regardless of the tabindex value.
✅ Correct: Style inner elements without making them focusable
<button type="button">
<span class="icon">★</span>
<span class="label">Favorite</span>
</button>
✅ Correct: Restructure if separate interactions are needed
If you need an icon and a separate action side by side, use sibling elements instead of nesting:
<span class="icon" aria-hidden="true">★</span>
<button type="button">Favorite</button>
This keeps the button as a clean, single interactive element while placing the decorative icon outside of it.
The aria-expanded attribute communicates to assistive technologies whether a related grouping element (such as a dropdown menu, accordion panel, or collapsible section) is currently expanded or collapsed. It accepts only three valid values:
- "true" — the controlled element is expanded and visible.
- "false" — the controlled element is collapsed and hidden.
- "undefined" — the element has no expandable relationship (this is also the implicit default when the attribute is omitted entirely).
This validation error typically occurs when the attribute is accidentally set to a non-boolean value. A common mistake is writing aria-expanded="aria-expanded", which mimics the old HTML4 pattern for boolean attributes like checked="checked". However, aria-expanded is not a standard HTML boolean attribute — it is an ARIA state attribute that requires an explicit string value of "true" or "false".
Setting an invalid value means assistive technologies like screen readers cannot correctly interpret the state of the control. A screen reader user may not know whether a menu is open or closed, leading to a confusing and inaccessible experience. Browsers may also handle the invalid value unpredictably, potentially treating it as truthy or ignoring it altogether.
How to fix it
- Identify the element with the invalid aria-expanded value.
- Replace the value with "true" if the associated content is currently expanded, or "false" if it is collapsed.
- If the button has no expand/collapse relationship at all, remove the aria-expanded attribute entirely.
- Ensure that JavaScript toggling logic updates the attribute to "true" or "false" — never to any other string.
Examples
❌ Invalid: attribute set to a non-boolean string
<button aria-expanded="aria-expanded" aria-controls="menu">
Toggle Menu
</button>
<ul id="menu">
<li>Option 1</li>
<li>Option 2</li>
</ul>
The value "aria-expanded" is not a recognized value and triggers the validation error.
✅ Fixed: attribute set to "false" (collapsed state)
<button aria-expanded="false" aria-controls="menu">
Toggle Menu
</button>
<ul id="menu" hidden>
<li>Option 1</li>
<li>Option 2</li>
</ul>
✅ Fixed: attribute set to "true" (expanded state)
<button aria-expanded="true" aria-controls="menu">
Toggle Menu
</button>
<ul id="menu">
<li>Option 1</li>
<li>Option 2</li>
</ul>
❌ Invalid: other common incorrect values
<!-- Using "yes" instead of "true" -->
<button aria-expanded="yes">Details</button>
<!-- Using "1" instead of "true" -->
<button aria-expanded="1">Details</button>
<!-- Empty value -->
<button aria-expanded="">Details</button>
All of these are invalid. The only accepted values are "true", "false", and "undefined".
✅ Toggling with JavaScript
When toggling aria-expanded dynamically, make sure the value is always set to the correct string:
<button aria-expanded="false" aria-controls="panel" onclick="togglePanel(this)">
Show details
</button>
<div id="panel" hidden>
<p>Additional details here.</p>
</div>
<script>
function togglePanel(button) {
const expanded = button.getAttribute("aria-expanded") === "true";
button.setAttribute("aria-expanded", String(!expanded));
const panel = document.getElementById(button.getAttribute("aria-controls"));
panel.hidden = expanded;
}
</script>
This ensures the attribute always toggles between "true" and "false", keeping the markup valid and the experience accessible for all users.
The ARIA in HTML specification defines which roles are allowed on each HTML element. Heading elements (<h1>–<h6>) have an implicit role of heading, and the set of roles they can be explicitly assigned is limited. The button role is not among them, so applying role="button" directly to a heading element is invalid.
This matters for several reasons. First, headings play a critical role in document structure and accessibility — screen reader users rely on headings to navigate and understand the page hierarchy. Assigning role="button" to a heading overrides its semantic meaning, which confuses assistive technologies. Second, browsers and screen readers may handle conflicting semantics unpredictably, leading to an inconsistent experience for users. Third, it violates the W3C HTML specification, which means your markup won’t pass validation.
Why This Combination Is Problematic
When you apply role="button" to an element, assistive technologies treat it as an interactive button. This completely replaces the element’s native heading semantics. Users who navigate by headings would no longer find that heading in their list, and users who navigate by interactive controls would encounter a “button” that may not behave like one (lacking keyboard support, focus management, etc.).
If you genuinely need something that looks like a heading but acts as a button, there are valid approaches to achieve this without breaking semantics.
How to Fix It
There are several strategies depending on your intent:
-
Use a <button> element styled as a heading. This is often the cleanest approach when the primary purpose is interactivity. You can style the button with CSS to match your heading appearance.
-
Wrap the heading in a <div> with role="button". This preserves the heading in the document outline while making the wrapper interactive. However, be aware that the button role applies role="presentation" to all descendant elements, meaning assistive technologies will strip the heading semantics from the <h2> inside it. The text content remains accessible, but it won’t be recognized as a heading.
-
Place a <button> inside the heading. This keeps the heading semantics intact for document structure while making the text inside it interactive. This pattern is commonly used for accordion-style components and is the approach recommended by the WAI-ARIA Authoring Practices.
Examples
❌ Invalid: role="button" on a heading
<h2 role="button">Toggle Section</h2>
This triggers the validation error because button is not an allowed role for heading elements.
✅ Fix: Use a <button> styled as a heading
<button type="button" class="heading-style">Toggle Section</button>
.heading-style {
font-size: 1.5em;
font-weight: bold;
background: none;
border: none;
cursor: pointer;
}
✅ Fix: Wrap the heading in a container with role="button"
<div role="button" tabindex="0">
<h2>Toggle Section</h2>
</div>
Note that this approach causes the <h2> to lose its heading semantics for assistive technologies, since the button role does not support semantic children. Also remember to add tabindex="0" so the element is keyboard-focusable, and implement keydown handlers for Enter and Space to replicate native button behavior.
✅ Fix: Place a <button> inside the heading (recommended for accordions)
<h2>
<button type="button" aria-expanded="false">
Toggle Section
</button>
</h2>
This is the most robust pattern. The heading remains in the document outline, and the button inside it is fully interactive with built-in keyboard support. Screen reader users can find it both when navigating by headings and when navigating by interactive elements. The aria-expanded attribute communicates whether the associated section is open or closed.
The <li> element has an implicit ARIA role of listitem, and the WHATWG HTML specification restricts which roles can be applied to it. The button role is not among the roles permitted on <li>. When you set role="button" on a <li>, you’re telling assistive technologies that the element is a button, but the browser and the spec still recognize it as a list item. This creates a semantic conflict that can confuse screen readers and other assistive tools, leading to a degraded experience for users who rely on them.
Beyond the validation error, there are practical accessibility concerns. A real <button> element comes with built-in keyboard support (it’s focusable and activatable with Enter or Space), whereas a <li> with role="button" lacks these behaviors by default. You would need to manually add tabindex, keyboard event handlers, and focus styling—effectively recreating what <button> gives you for free. This is error-prone and violates the ARIA principle of preferring native HTML elements over ARIA role overrides.
How to Fix
There are several approaches depending on your use case:
- Place a <button> inside each <li> — This is the best approach when you have a list of actions, as it preserves list semantics while providing proper button functionality.
- Use <button> elements directly — If the items aren’t truly a list, drop the <ul>/<li> structure and use <button> elements instead.
- Use a <div> or <span> with role="button" — If you cannot use a native <button> for some reason, these elements accept the button role. You’ll also need to add tabindex="0" and keyboard event handling yourself.
Examples
❌ Invalid: role="button" on <li> elements
<ul>
<li role="button">Copy</li>
<li role="button">Paste</li>
<li role="button">Delete</li>
</ul>
This triggers the validation error because <li> does not permit the button role.
✅ Fixed: Using <button> elements inside <li>
<ul>
<li><button type="button">Copy</button></li>
<li><button type="button">Paste</button></li>
<li><button type="button">Delete</button></li>
</ul>
This preserves the list structure while providing proper, accessible button behavior with no extra work.
✅ Fixed: Using standalone <button> elements
If the list structure isn’t meaningful, remove it entirely:
<div>
<button type="button">Copy</button>
<button type="button">Paste</button>
<button type="button">Delete</button>
</div>
✅ Fixed: Using a toolbar pattern
For a group of related actions, the ARIA toolbar pattern is a great fit:
<div role="toolbar" aria-label="Text actions">
<button type="button">Copy</button>
<button type="button">Paste</button>
<button type="button">Delete</button>
</div>
✅ Fixed: Using role="button" on a permitted element
If you truly cannot use a native <button>, a <div> or <span> can accept the button role. Note that you must manually handle focus and keyboard interaction:
<div role="button" tabindex="0">Copy</div>
However, this approach is almost always inferior to using a native <button> and should only be used as a last resort. Native elements provide keyboard behavior, form integration, and consistent styling hooks that are difficult to replicate reliably.
The HTML specification defines <button> as a versatile interactive element, but its behavior changes depending on context. When a <button> is placed inside a <form> without a type attribute, it defaults to type="submit", which can cause unexpected form submissions. The validator flags this because relying on the implicit default is ambiguous and error-prone. Explicitly setting the type attribute makes the button’s intent clear to both developers and browsers.
The three valid values for the type attribute are:
- submit — submits the parent form’s data to the server.
- reset — resets all form controls to their initial values.
- button — performs no default action; behavior is defined via JavaScript.
When a <button> is given an ARIA role of checkbox, switch, or menuitemcheckbox, the validator expects an aria-checked attribute to accompany it. These roles describe toggle controls that have a checked or unchecked state, so assistive technologies need to know the current state. Without aria-checked, screen readers cannot communicate whether the control is on or off, making the interface inaccessible.
The aria-checked attribute accepts the following values:
- true — the control is checked or on.
- false — the control is unchecked or off.
- mixed — the control is in an indeterminate state (valid for checkbox and menuitemcheckbox roles only).
How to fix it
For standard buttons, add the type attribute with the appropriate value. If the button triggers JavaScript behavior and is not meant to submit a form, use type="button". If it submits a form, use type="submit" explicitly to make the intent clear.
For toggle buttons, ensure the <button> has both a role attribute (such as checkbox or switch) and an aria-checked attribute that reflects the current state. You should also include type="button" to prevent unintended form submission. Use JavaScript to toggle the aria-checked value when the user interacts with the button.
Examples
Missing type attribute
This triggers the validator warning because the type is not specified:
<form action="/search">
<input type="text" name="q">
<button>Search</button>
</form>
Fixed by adding an explicit type:
<form action="/search">
<input type="text" name="q">
<button type="submit">Search</button>
</form>
Button used outside a form without type
<button onclick="openMenu()">Menu</button>
Fixed by specifying type="button":
<button type="button" onclick="openMenu()">Menu</button>
Toggle button missing aria-checked
A button with role="switch" but no aria-checked attribute:
<button type="button" role="switch">Dark Mode</button>
Fixed by adding aria-checked:
<button type="button" role="switch" aria-checked="false">Dark Mode</button>
Checkbox-style toggle button
A button acting as a checkbox must include both role="checkbox" and aria-checked:
<button type="button" role="checkbox" aria-checked="false">
Enable notifications
</button>
Complete toggle example with all required attributes
<button type="button" role="switch" aria-checked="false" id="wifi-toggle">
Wi-Fi
</button>
<script>
document.getElementById("wifi-toggle").addEventListener("click", function () {
const isChecked = this.getAttribute("aria-checked") === "true";
this.setAttribute("aria-checked", String(!isChecked));
});
</script>
In this example, the type="button" prevents form submission, the role="switch" tells assistive technologies this is a toggle, and aria-checked is updated dynamically to reflect the current state. This ensures the button is fully accessible and passes validation.
The HTML specification explicitly forbids nesting interactive content inside a <button> element. This means a <button> cannot contain another <button>, nor can it contain elements like <a>, <input>, <select>, <textarea>, or any other interactive element. The <button> element’s content model allows phrasing content, but specifically excludes interactive content.
The most common cause of this error is a missing </button> closing tag. When you forget to close a button, the parser considers everything that follows — including the next <button> start tag — as being inside the first button. This creates an invalid nesting situation even though you likely intended the buttons to be siblings.
This matters for several reasons:
- Accessibility: Screen readers and assistive technologies rely on proper document structure. A button nested inside another button creates a confusing interaction model — which button does the user activate when they click or press Enter?
- Browser behavior: Browsers handle this invalid markup inconsistently. Some may ignore the inner button, while others may attempt error recovery in unpredictable ways, leading to broken functionality.
- Standards compliance: Invalid HTML can interfere with JavaScript event handling, CSS styling, and overall page reliability.
Examples
Missing closing tag (triggers the error)
<button>Submit
<button>Cancel</button>
Here, the first <button> is never closed, so the parser sees the second <button> as being nested inside it.
Intentionally nested buttons (triggers the error)
<button class="card">
<span>Select this plan</span>
<button class="details">More info</button>
</button>
Even if this nesting is intentional, it is invalid HTML. A <button> cannot contain another <button>.
Fix: Close each button properly
<button>Submit</button>
<button>Cancel</button>
Adding the missing </button> tag makes the two buttons proper siblings.
Fix: Restructure nested interactive elements
If you need a clickable card with an additional action inside it, separate the interactive elements rather than nesting them:
<div class="card">
<button class="select">Select this plan</button>
<button class="details">More info</button>
</div>
Fix: Use non-interactive elements for styling purposes
If the inner element was only meant for visual grouping, use a <span> or other phrasing element instead of a <button>:
<button class="card">
<span class="label">Select this plan</span>
<span class="sublabel">Best value</span>
</button>
This keeps a single interactive <button> while using non-interactive <span> elements for internal structure and styling.
Every HTML element carries an implicit ARIA role that communicates its purpose to assistive technologies like screen readers. The <button> element natively has the button role built in, so explicitly adding role="button" is redundant. The W3C validator flags this as unnecessary because it adds no information — assistive technologies already understand that a <button> is a button.
The role attribute exists primarily to assign interactive semantics to elements that don’t have them natively. For example, you might add role="button" to a <div> or <span> that has been styled and scripted to behave like a button (though using a native <button> is always preferable). When you apply it to an element that already carries that role by default, it creates noise in your code and can signal to other developers that something unusual is going on — when in fact nothing is.
This principle applies broadly across HTML. Other examples of redundant roles include role="link" on an <a> element with an href, role="navigation" on a <nav> element, and role="heading" on an <h1> through <h6> element. The WAI-ARIA specification refers to these as “default implicit ARIA semantics,” and the general rule is: don’t set an ARIA role that matches the element’s native semantics.
Removing redundant roles keeps your markup clean, easier to maintain, and avoids potential confusion during code reviews or audits. It also aligns with the first rule of ARIA: “If you can use a native HTML element with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state, or property to make it accessible, then do so.”
How to fix it
Remove the role="button" attribute from any <button> element. No replacement is needed — the native semantics are already correct.
If you have a non-button element (like a <div>) that uses role="button", consider replacing it with a real <button> element instead. This gives you built-in keyboard support, focus management, and form submission behavior for free.
Examples
❌ Redundant role on a button
<button role="button">Buy now</button>
<button type="submit" role="button">Submit</button>
Both of these trigger the validator warning because role="button" duplicates what the <button> element already communicates.
✅ Button without redundant role
<button>Buy now</button>
<button type="submit">Submit</button>
Simply removing the role attribute resolves the issue. The element’s native semantics handle everything.
❌ Using role=”button” on a non-semantic element
<div role="button" tabindex="0" onclick="handleClick()">Buy now</div>
While this is technically valid and won’t trigger the same warning, it requires manual handling of keyboard events, focus styles, and accessibility states.
✅ Using a native button instead
<button onclick="handleClick()">Buy now</button>
A native <button> provides keyboard interaction (Enter and Space key activation), focusability, and correct role announcement — all without extra attributes or JavaScript.
Other common redundant roles to avoid
<!-- ❌ Redundant -->
<a href="/about" role="link">About</a>
<nav role="navigation">...</nav>
<h1 role="heading">Title</h1>
<input type="checkbox" role="checkbox">
<!-- ✅ Clean -->
<a href="/about">About</a>
<nav>...</nav>
<h1>Title</h1>
<input type="checkbox">
The WAI-ARIA specification defines implicit roles (also called “native semantics”) for many HTML elements. An <input> element with type="submit" inherently communicates to assistive technologies that it is a button control. Adding role="button" explicitly restates what the browser and screen readers already know, making it redundant.
The role="button" attribute is designed for situations where you need to make a non-interactive element — such as a <div> or <span> — behave like a button for assistive technologies. When applied to elements that already carry this semantic meaning natively, it adds unnecessary noise to your markup without providing any accessibility benefit.
Why this is a problem
- Redundancy: The explicit role duplicates the element’s built-in semantics, cluttering the HTML with no added value.
- Maintenance risk: Redundant ARIA attributes can mislead other developers into thinking the role is necessary, or that the element’s native semantics differ from what they actually are.
- Standards compliance: The W3C validator flags this as an issue because the ARIA in HTML specification explicitly states that authors should not set ARIA roles or attributes that match an element’s implicit native semantics. This principle is sometimes called the “first rule of ARIA” — don’t use ARIA when a native HTML element already provides the semantics you need.
- Potential conflicts: While current browsers handle redundant roles gracefully, explicitly overriding native semantics can theoretically interfere with future browser or assistive technology behavior.
How to fix it
Remove the role="button" attribute from any <input type="submit"> element. The same principle applies to other input types with implicit roles, such as <input type="reset"> (which also has an implicit button role) and <button> elements.
Examples
❌ Incorrect: redundant role="button" on a submit input
<form action="/checkout" method="post">
<input type="submit" role="button" value="Buy Now">
</form>
✅ Correct: no explicit role needed
<form action="/checkout" method="post">
<input type="submit" value="Buy Now">
</form>
❌ Incorrect: redundant role on a <button> element
The same issue applies to <button> elements, which also have an implicit button role:
<button type="submit" role="button">Submit Order</button>
✅ Correct: let native semantics do the work
<button type="submit">Submit Order</button>
✅ Correct: using role="button" where it is appropriate
The role="button" attribute is meaningful when applied to an element that does not natively convey button semantics. Note that you must also handle keyboard interaction and focus management manually in this case:
<div role="button" tabindex="0">Add to Cart</div>
Even in this scenario, using a native <button> element is strongly preferred over adding ARIA roles to non-interactive elements, since the native element provides built-in keyboard support and focus behavior for free.
The <summary> element serves as the clickable disclosure toggle for a <details> element. Because its built-in behavior is inherently interactive — clicking it expands or collapses the parent <details> content — the HTML specification assigns it an implicit button role. This means assistive technologies like screen readers already announce <summary> as a button without any additional markup.
When you explicitly add role="button" to a <summary> element, the W3C validator flags it as unnecessary. While this doesn’t cause functional problems, redundant ARIA roles are discouraged by the first rule of ARIA use: if an HTML element already has the semantics you need, don’t re-add them with ARIA attributes. Redundant roles add noise to your code, can confuse other developers into thinking custom behavior is being applied, and in edge cases may interact unexpectedly with certain assistive technologies.
This principle applies broadly — many HTML elements have implicit roles (e.g., <nav> has navigation, <main> has main, <button> has button). Adding the role they already carry is always unnecessary.
How to fix it
Remove the role="button" attribute from the <summary> element. No replacement is needed since the semantics are already built in.
Examples
❌ Incorrect: redundant role="button" on <summary>
<details>
<summary role="button">Show more information</summary>
<p>Here is the additional information that was hidden.</p>
</details>
The validator will report: The “button” role is unnecessary for element “summary”.
✅ Correct: <summary> without an explicit role
<details>
<summary>Show more information</summary>
<p>Here is the additional information that was hidden.</p>
</details>
The <summary> element’s implicit button role ensures assistive technologies already treat it as an interactive control. No additional attributes are required.
✅ Correct: a more complete <details> example
<details>
<summary>I have keys but no doors. I have space but no room. You can enter but can't leave. What am I?</summary>
<p>A keyboard.</p>
</details>
Clicking the <summary> toggles the parent <details> element between its open and closed states. Screen readers announce it as a button automatically, and keyboard users can activate it with <kbd>Enter</kbd> or <kbd>Space</kbd> — all without any explicit ARIA role.
The 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 the role="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 with role="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 HTML specification defines the <button> element’s content model as “phrasing content” but explicitly excludes interactive content. Since the <a> element (when it has an href attribute) is classified as interactive content, nesting it inside a <button> violates this rule. The same restriction applies to any element that has role="button", as it semantically functions as a button.
This is problematic for several reasons:
- Accessibility: Screen readers and assistive technologies cannot reliably convey the purpose of nested interactive elements. A user tabbing through the page may encounter confusing or duplicate focus targets, and the intended action becomes ambiguous.
- Unpredictable behavior: Browsers handle nested interactive elements inconsistently. Clicking the link inside a button might trigger the button’s click handler, the link’s navigation, both, or neither — depending on the browser.
- Standards compliance: The HTML specification forbids this nesting to ensure a clear, unambiguous interaction model for all users and user agents.
To fix this, decide what the element should do. If it navigates to a URL, use an <a> element (styled as a button if needed). If it performs an action like submitting a form or toggling something, use a <button>. If you need both behaviors, handle navigation programmatically via JavaScript on a <button>, or separate them into two distinct elements.
Examples
❌ Incorrect: link nested inside a button
<button>
<a href="/dashboard">Go to Dashboard</a>
</button>
❌ Incorrect: link inside an element with role="button"
<div role="button">
<a href="/settings">Settings</a>
</div>
✅ Correct: use an anchor styled as a button
If the goal is navigation, use an <a> element and style it to look like a button with CSS:
<a href="/dashboard" class="button">Go to Dashboard</a>
.button {
display: inline-block;
padding: 8px 16px;
background-color: #007bff;
color: #fff;
text-decoration: none;
border-radius: 4px;
cursor: pointer;
}
✅ Correct: use a button with JavaScript navigation
If you need button semantics but also want to navigate, handle the navigation in JavaScript:
<button type="button" onclick="location.href='/dashboard'">
Go to Dashboard
</button>
✅ Correct: separate the two elements
If both a button action and a link are genuinely needed, place them side by side:
<button type="button">Save</button>
<a href="/dashboard">Go to Dashboard</a>
✅ Correct: link without href inside a button (edge case)
An <a> element without an href attribute is not interactive content, so it is technically valid inside a <button>. However, this is rarely useful in practice:
<button type="button">
<a>Label text</a>
</button>
As a general rule, never nest one clickable element inside another. This applies not only to <a> inside <button>, but also to other combinations like <button> inside <a>, or <a> inside <a>. Keeping interactive elements separate ensures predictable behavior and a good experience for all users.
The role="button" attribute tells assistive technologies like screen readers that an element behaves as a button — a widget used to perform actions such as submitting a form, opening a dialog, or triggering a command. When a <button> element appears inside an element with role="button", the result is a nested interactive control. The HTML specification explicitly forbids this because interactive content must not be nested within other interactive content.
This nesting causes real problems. Screen readers may announce the outer element as a button but fail to recognize or reach the inner <button>. Keyboard users may not be able to focus on or activate the inner control. Different browsers handle the situation inconsistently — some may ignore one of the controls entirely, others may fire events on the wrong element. The end result is an interface that is broken for many users.
This issue commonly arises in a few scenarios:
- A <div> or <span> is given role="button" and then a <button> is placed inside it for styling or click-handling purposes.
- A component library wraps content in a role="button" container, and a developer adds a <button> inside without realizing the conflict.
- A custom card or list item is made clickable with role="button", but also contains action buttons within it.
The fix depends on your intent. If the outer element is the intended interactive control, remove the inner <button> and handle interactions on the outer element. If the inner <button> is the intended control, remove role="button" from the ancestor. If both need to be independently clickable, restructure the markup so neither is a descendant of the other.
Examples
❌ Incorrect: <button> inside an element with role="button"
<div role="button" tabindex="0" onclick="handleClick()">
<button type="button">Click me</button>
</div>
This is invalid because the <button> is a descendant of the <div> that has role="button".
✅ Fix option 1: Use only the <button> element
If the inner <button> is the actual control, remove role="button" from the wrapper:
<div>
<button type="button" onclick="handleClick()">Click me</button>
</div>
✅ Fix option 2: Use only the outer role="button" element
If the outer element is the intended interactive control, remove the inner <button>:
<div role="button" tabindex="0" onclick="handleClick()">
Click me
</div>
Note that when using role="button" on a non-<button> element, you must also handle keyboard events (Enter and Space) manually. A native <button> provides this for free, so prefer option 1 when possible.
❌ Incorrect: Clickable card containing action buttons
<div role="button" tabindex="0" class="card">
<h3>Item title</h3>
<p>Description text</p>
<button type="button">Delete</button>
</div>
✅ Fix: Separate the card link from the action buttons
<div class="card">
<h3><button type="button" class="card-link">Item title</button></h3>
<p>Description text</p>
<button type="button">Delete</button>
</div>
In this approach, the card’s main action is handled by a <button> on the title, while the “Delete” button remains an independent control. Neither is nested inside the other, and both are accessible to keyboard and screen reader users.
The HTML living standard defines both <a> and <button> as interactive content. Interactive content elements cannot be descendants of other interactive content elements. When you place a <button> inside an <a>, you create an ambiguous situation: should a click activate the link navigation or the button action? Browsers handle this inconsistently, which leads to unpredictable behavior for all users.
This is especially problematic for accessibility. Screen readers and other assistive technologies rely on a clear, well-defined element hierarchy to communicate the purpose of controls to users. A button nested inside a link creates a confusing experience — the user may hear both a link and a button announced, with no clear indication of what will actually happen when they activate it. Keyboard navigation can also break, as focus behavior becomes unreliable.
The same rule applies to elements that aren’t literally <button> but carry role="button". For example, a <span role="button"> inside an <a> tag triggers the same validation error, because the ARIA role makes it semantically interactive.
How to Fix It
The fix depends on what you’re trying to achieve:
- If the element should navigate to a URL, use an <a> element and style it to look like a button with CSS. Remove the <button> entirely.
- If the element should perform a JavaScript action, use a <button> element and remove the wrapping <a>. Attach the action via an event listener.
- If you need both a link and a button, place them side by side as siblings rather than nesting one inside the other.
Examples
❌ Invalid: Button inside a link
<a href="/dashboard">
<button>Go to Dashboard</button>
</a>
✅ Fixed: Link styled as a button
If the goal is navigation, use a link and style it with CSS:
<a href="/dashboard" class="btn">Go to Dashboard</a>
.btn {
display: inline-block;
padding: 8px 16px;
background-color: #007bff;
color: #fff;
text-decoration: none;
border-radius: 4px;
}
✅ Fixed: Button with a JavaScript action
If the goal is to trigger an action (like navigating programmatically), use a button on its own:
<button type="button" onclick="window.location.href='/dashboard'">
Go to Dashboard
</button>
❌ Invalid: Element with role="button" inside a link
<a href="/settings">
<span role="button">Settings</span>
</a>
✅ Fixed: Remove the redundant role
Since the <a> element already communicates interactivity, the inner role="button" is unnecessary and conflicting. Simply use the link directly:
<a href="/settings">Settings</a>
❌ Invalid: Link inside a button
Note that the reverse — an <a> inside a <button> — is also invalid for the same reason:
<button>
<a href="/home">Home</a>
</button>
✅ Fixed: Choose one element
<a href="/home" class="btn">Home</a>
The key principle is simple: never nest one interactive element inside another. Pick the element that best matches the semantics of your use case — <a> for navigation, <button> for actions — and use CSS to achieve the visual design you need.
The ARIA button role tells browsers and assistive technologies that an element behaves like a button. According to the WAI-ARIA specification, elements with role="button" follow the same content restrictions as native <button> elements. Specifically, they must not contain interactive content as descendants. The <input> element is considered interactive content, so nesting it inside any element with role="button" is invalid.
This restriction exists for important accessibility and usability reasons. When a screen reader encounters an element with role="button", it announces it as a single actionable control. If that button contains another interactive element like an <input>, the user faces conflicting interactions — should activating the element trigger the button action or interact with the input? This ambiguity confuses both assistive technologies and users. Browsers may also handle focus and click events unpredictably when interactive elements are nested this way.
The same rule applies to native <button> elements, <a> elements with an href, and any other element that is already interactive. Adding role="button" to a <div> or <span> elevates it to the same status, so the same nesting restrictions apply.
To fix this issue, consider these approaches:
- Move the <input> outside the button-role element and position them as siblings.
- Replace the <input> with non-interactive content such as a <span> styled to look like the desired control, with appropriate ARIA attributes to convey state.
- Rethink the component structure — if you need both a button action and an input, they should be separate controls that are visually grouped but not nested.
Examples
❌ Invalid: <input> nested inside an element with role="button"
<div role="button" tabindex="0">
<input type="checkbox" />
Accept terms
</div>
❌ Invalid: <input> nested inside a native <button>
<button>
<input type="text" />
Search
</button>
✅ Valid: Separate the <input> and button into sibling elements
<label>
<input type="checkbox" />
Accept terms
</label>
<button>Submit</button>
✅ Valid: Use non-interactive content inside the button-role element
If you want a toggle-style button that conveys checked/unchecked state, use ARIA attributes on the button itself instead of embedding an <input>:
<div role="button" tabindex="0" aria-pressed="false">
<span aria-hidden="true">☐</span>
Accept terms
</div>
✅ Valid: Use a <label> and <input> alongside a button
<div>
<label>
<input type="checkbox" />
Accept terms
</label>
<div role="button" tabindex="0">Continue</div>
</div>
✅ Valid: Button with only non-interactive phrasing content
<div role="button" tabindex="0">
<span>Click me</span>
</div>
When in doubt, keep interactive elements as separate, distinct controls rather than nesting them. This ensures clear semantics, predictable behavior across browsers, and a good experience for users of assistive technologies.
An input element cannot be placed inside a button element because the HTML standard prohibits interactive content as descendants of a button.
According to the HTML specification, interactive content (like input, button, select, etc.) must not be nested inside button elements. This restriction exists to prevent invalid or unpredictable behavior in user interfaces. For accessible and valid markup, each form control should be a separate, direct child of its container, not nested inside another interactive element.
Example of invalid HTML
<button>
Submit
<input type="text" name="example">
</button>
Correct HTML structure
<form>
<input type="text" name="example">
<button type="submit">Submit</button>
</form>
Always ensure that form elements such as input and button are not nested within each other. Keep them as siblings within a form or appropriate container.
The <label> element serves a specific purpose in HTML: it represents a caption for a form control. It can be associated with a control either through the for attribute (referencing the control’s id) or by wrapping the form control inside the <label> itself. Placing a <label> inside a <button> is semantically incorrect because a button is not a form control that benefits from labeling in this way — the button’s own text content or aria-label attribute already serves as its accessible name.
The HTML specification defines the content model of <button> as phrasing content, but explicitly excludes interactive content. Since <label> is classified as interactive content (it directs focus to its associated control when clicked), nesting it inside a <button> creates ambiguous behavior. When a user clicks the label, should it activate the button, or should it shift focus to the label’s associated control? Browsers handle this conflict inconsistently, which leads to unpredictable user experiences.
From an accessibility standpoint, this nesting is problematic because screen readers may announce the button in a confusing way, potentially reading it as containing a label for another element. The button’s accessible name should come directly from its text content, not from a nested <label>.
To fix this issue, simply replace the <label> with a <span> or plain text inside the button. If you need to style part of the button’s text differently, <span> elements are perfectly valid inside buttons.
Examples
❌ Invalid: <label> inside a <button>
<button type="submit">
<label>Submit Form</label>
</button>
❌ Invalid: <label> with a for attribute inside a <button>
<button type="button">
<label for="file-input">Choose File</label>
</button>
<input type="file" id="file-input">
✅ Fixed: Use plain text inside the <button>
<button type="submit">
Submit Form
</button>
✅ Fixed: Use a <span> for styling purposes
<button type="submit">
<span class="button-text">Submit Form</span>
</button>
✅ Fixed: Separate the <label> and <button>
If you need a label to describe a button’s purpose in a form, place the <label> outside the button and use the for attribute to associate it with a related input, or use aria-label on the button itself:
<label for="username">Username</label>
<input type="text" id="username">
<button type="submit" aria-label="Submit the username form">
Submit
</button>
Ready to validate your sites?
Start your free trial today.