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.
When an element has role="button", assistive technologies treat it as a single interactive control — just like a native <button>. Users expect to tab to it once, and then activate it with Enter or Space. If a focusable descendant (an element with tabindex) exists inside that button, it creates a second tab stop within what should be a single control. This breaks the expected interaction model and confuses both keyboard users and screen readers.
The WAI-ARIA specification explicitly states that certain roles, including button, must not contain interactive or focusable descendants. This is because a button is an atomic widget — it represents one action and should receive focus as a single unit. When a screen reader encounters a role="button" element, it announces it as a button and expects the user to interact with it directly. A nested focusable element disrupts this by creating an ambiguous focus target: should the user interact with the outer button or the inner focusable element?
This issue commonly arises when developers wrap inline elements like <span> or <a> with tabindex inside a <div role="button">, often to style parts of the button differently or to add click handlers. The correct approach is to ensure only the outermost button-like element is focusable.
How to fix it
Use a native
<button>element. This is always the best solution. Native buttons handle focus, keyboard interaction (Enter and Space key activation), and accessibility announcements automatically — noroleortabindexneeded.Move
tabindexto therole="button"container. If you must userole="button"(for example, when a<div>needs to behave as a button due to design constraints), placetabindex="0"on the container itself and removetabindexfrom all descendants.Remove
tabindexfrom descendants. If the inner element doesn't actually need to be independently focusable, simply remove thetabindexattribute from it.
When using role="button" on a non-interactive element, remember you also need to implement keyboard event handlers for Enter and Space to fully replicate native button behavior.
Examples
Incorrect: focusable descendant inside role="button"
<div role="button">
<span tabindex="0">Click me</span>
</div>
The <span> with tabindex="0" creates a focusable element inside the role="button" container, which violates the ARIA authoring rules.
Incorrect: anchor element inside role="button"
<div role="button" tabindex="0">
<a href="/action" tabindex="0">Perform action</a>
</div>
Even though the container itself is focusable, the nested <a> with tabindex is also focusable, creating two tab stops for what should be a single control.
Correct: use a native <button> element
<button>Click me</button>
A native <button> handles focus, keyboard events, and accessibility semantics out of the box with no additional attributes.
Correct: move tabindex to the role="button" container
<div role="button" tabindex="0">
<span>Click me</span>
</div>
The tabindex="0" is on the role="button" element itself, and the inner <span> is not independently focusable.
Correct: native button with styled inner content
<button>
<span class="icon">★</span>
<span class="label">Favorite</span>
</button>
You can still use inner elements for styling purposes inside a <button> — just don't add tabindex to them. The button manages focus as a single unit, and screen readers announce the combined text content.
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.
An element with a tabindex attribute is nested inside an element whose ARIA role, such as button, tab or checkbox, describes a single control that should take focus as one unit.
ARIA in HTML gives an element with one of these roles the same content rules as the native control it imitates. A <button> can hold text and other phrasing content, but no interactive elements and no elements with a tabindex attribute. A <div role="button"> follows the same rule.
The value of tabindex doesn't matter. With tabindex="0", the inner element becomes a second tab stop inside the control. With tabindex="-1", it stays out of the tab order, but a script or a mouse click can still focus it, so focus lands on part of the control instead of the control itself. Keyboard and screen reader users expect to reach the control once and activate it with Enter or Space. Most of these roles also have presentational children in WAI-ARIA, which means browsers shouldn't expose their descendants to assistive technologies as separate elements.
The validator checks descendants at any depth inside elements with role set to button, checkbox, img, link, math, menuitem, menuitemcheckbox, menuitemradio, option, progressbar, radio, separator, slider, switch or tab. The element with the role can have tabindex itself; the validator only flags its descendants.
It's a warning because the ARIA in HTML rule behind it is non-normative. Older validator builds reported it as an error, with "must not appear" in place of "should not appear". Inside a native <button> or <a>, a descendant with tabindex is still an error.
Remove tabindex from the descendant, and keep tabindex="0" on the element with the role. A native <button> is simpler, because it's focusable and responds to Enter and Space without extra attributes or scripts. Some carousel scripts give each slide role="button" and an inner tabindex="-1" element as a focus target. A slide with its own content, like an image and a caption, isn't a button, so remove role="button" from the slide and put a real <button> inside it for the action.
Invalid example
<div role="button" tabindex="0">
<span class="label" tabindex="-1">Add to cart</span>
</div>
Valid example
<button type="button">
<span class="label">Add to cart</span>
</button>
Valid example keeping the role
<div role="button" tabindex="0">
<span class="label">Add to cart</span>
</div>
A label element with a for attribute contains a form control whose id is missing or doesn't match the for value.
A label can be tied to its control in two ways: by wrapping the control, or by naming the control's id in for. When for is present, it alone picks the labeled control, and the label may not contain any other labelable element. The labelable elements are button, input (except type="hidden"), meter, output, progress, select and textarea, and the message names the one that broke the rule.
The browser follows for and ignores the nested control. If for points at an id that doesn't exist, the label has no control at all: clicking its text does nothing, and the control inside can end up without an accessible name.
A button usually gets here through a search form that wraps both the search field and its submit button in one <label for="search-field">. The field's id matches, but the button can't share it because every id in a page must be unique. The validator also reports that the label holds more than one labelable descendant, and screen readers may read the button's text as part of the field's name.
Close the label before the button, or move the button out of it, so the label holds only the control it names. When the label wraps a single control, either give that control an id equal to the for value, or remove for and let the wrapping make the association.
Invalid example
<form role="search" action="/search">
<label for="search-field">
Search for:
<input type="search" id="search-field" name="q">
<button type="submit">Search</button>
</label>
</form>
Valid example
<form role="search" action="/search">
<label for="search-field">
Search for:
<input type="search" id="search-field" name="q">
</label>
<button type="submit">Search</button>
</form>
A control with the disabled attribute is already exposed as disabled, so aria-disabled repeats the same information and can be removed.
The disabled attribute works on <button>, <input>, <select>, <textarea>, <fieldset>, <optgroup> and <option>. Browsers take a disabled control out of the tab order, ignore clicks on it, and tell assistive technologies that it is disabled. Adding aria-disabled="true" says the same thing again. The ARIA in HTML specification advises against it, and the validator reports it as a warning.
You also get this warning when aria-disabled copies the value of disabled, as in disabled="disabled" aria-disabled="disabled", or when an empty aria-disabled="" sits next to a bare disabled. The validator then adds a Bad value error as well, because aria-disabled only accepts true or false.
Remove aria-disabled and keep disabled. If aria-disabled is set to false on an element with disabled, the two attributes contradict each other, and the validator reports that as an error instead.
Sometimes a button should stay focusable while it is unavailable, so keyboard and screen reader users can still reach it and find out why. For that, drop disabled and keep only aria-disabled="true". The validator accepts this, but aria-disabled changes nothing about behavior: the button still responds to clicks and still submits the form, so your script has to ignore those activations while aria-disabled is true.
Invalid example
<form action="/checkout" method="post">
<label for="email">Email</label>
<input type="email" id="email" name="email">
<button type="submit" disabled aria-disabled="true">Place order</button>
</form>
Valid example
<form action="/checkout" method="post">
<label for="email">Email</label>
<input type="email" id="email" name="email">
<button type="submit" disabled>Place order</button>
</form>
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-expandedvalue. - 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-expandedattribute 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 role="button" attribute is not allowed on the article element because article already has an implicit ARIA role of article, and button is not among the roles permitted on this element.
The article element represents a self-contained composition in a document, such as a blog post, a news story, or a forum comment. Its implicit ARIA role is article. According to the ARIA in HTML specification, the article element can only use a limited set of roles: application, document, feed, main, none, presentation, or region. The button role is not in that list.
The button role turns an element into an interactive control that users can activate, which clashes with article, a container for standalone content. If you need a clickable control, use a real <button> element so you get keyboard support and focus handling for free. If you need the semantics of article, keep the element and remove the role.
Example with the issue
<article role="button">
<h2>Latest news</h2>
<p>Content of the article.</p>
</article>
How to fix it
If you need a control the user can activate, use a <button>:
<button type="button">Read the latest news</button>
If you need the semantics of article, drop the conflicting role:
<article>
<h2>Latest news</h2>
<p>Content of the article.</p>
</article>
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>withrole="button". This preserves the heading in the document outline while making the wrapper interactive. However, be aware that thebuttonrole appliesrole="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>withrole="button"— If you cannot use a native<button>for some reason, these elements accept thebuttonrole. You'll also need to addtabindex="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 aria-selected attribute only accepts the values true, false and undefined, written in lowercase.
aria-selected tells assistive technologies which item in a set is the selected one, such as the active tab in a tab list. It is an ARIA state, not an HTML boolean attribute, so the old habit of repeating the attribute name as its value (checked="checked", selected="selected") doesn't work here. Writing aria-selected="aria-selected" is the most common way to trigger this error. The value undefined is allowed and means the same as leaving the attribute out.
The validator rejects every other value too: selected, yes, 1, TRUE, an empty value, or true with spaces around it. A screen reader can't tell from such a value whether the tab is selected, so users may not hear which one is active. Scripts that switch tabs cause this when they write anything other than the strings "true" and "false" into the attribute.
Set aria-selected="true" on the selected item and aria-selected="false" on the others. On a button, the attribute only has a meaning when the button has a role that supports it, usually role="tab" inside an element with role="tablist". On a button without a role, the validator also reports that the button is missing a role attribute. For a button that turns something on and off, use aria-pressed instead.
Invalid example
<div role="tablist" aria-label="Billing period">
<button role="tab" id="tab-monthly" aria-controls="panel-monthly" aria-selected="aria-selected">Monthly</button>
<button role="tab" id="tab-yearly" aria-controls="panel-yearly">Yearly</button>
</div>
<div role="tabpanel" id="panel-monthly" aria-labelledby="tab-monthly">Monthly prices</div>
<div role="tabpanel" id="panel-yearly" aria-labelledby="tab-yearly" hidden>Yearly prices</div>
Valid example
<div role="tablist" aria-label="Billing period">
<button role="tab" id="tab-monthly" aria-controls="panel-monthly" aria-selected="true">Monthly</button>
<button role="tab" id="tab-yearly" aria-controls="panel-yearly" aria-selected="false">Yearly</button>
</div>
<div role="tabpanel" id="panel-monthly" aria-labelledby="tab-monthly">Monthly prices</div>
<div role="tabpanel" id="panel-yearly" aria-labelledby="tab-yearly" hidden>Yearly prices</div>
A <button> element accepts only a short list of ARIA roles in its role attribute, and the validator rejects every other value.
The ARIA in HTML specification allows these roles on a <button>: checkbox, combobox, gridcell, link, menuitem, menuitemcheckbox, menuitemradio, option, radio, separator, slider, switch, tab and treeitem. Anything else fails: real ARIA roles like heading or img, made-up words like btn, and the presentational roles none and presentation. Some of the allowed roles bring their own requirements, such as aria-checked for switch, which the validator reports as separate errors.
role="none" usually shows up on a <button> that is only decoration, like the plus or chevron icon of an accordion, often next to tabindex="-1" and aria-hidden="true" to keep it away from keyboard and screen reader users. The role adds nothing there. Browsers ignore none and presentation on elements that can receive focus, buttons included, so the element keeps its button role.
If the element isn't a control, it shouldn't be a <button>. Put the icon in a <span> with aria-hidden="true", inside the real button, so the whole row stays clickable and screen readers skip the icon. If the element is a control, remove the role attribute, or replace it with one of the allowed roles that describes what the button does.
Invalid example
<div class="faq-question">
<h3>
<button type="button" aria-expanded="false" aria-controls="answer-1">How long does shipping take?</button>
</h3>
<button type="button" class="faq-icon" role="none" tabindex="-1" aria-hidden="true">+</button>
</div>
<div id="answer-1" hidden>Orders ship within two business days.</div>
Valid example
<h3>
<button type="button" aria-expanded="false" aria-controls="answer-1">
How long does shipping take?
<span class="faq-icon" aria-hidden="true">+</span>
</button>
</h3>
<div id="answer-1" hidden>Orders ship within two business days.</div>
The type attribute on a <button> only accepts three keywords — submit, reset, and button — so any other value is rejected.
type is an enumerated attribute, not a free-form one. submit posts the form (and is what a button does when the attribute is absent), reset clears the form fields, and button does nothing on its own and is meant to be wired up with JavaScript. Anything outside that set is invalid markup, and the browser falls back to submit, so a button you expected to stay inert may submit the form instead.
If you want the default behavior, leave the attribute off or write type="submit" explicitly; if the button should not submit, use type="button".
Invalid example
<button type="default">Save</button>
Valid example
<button type="submit">Save</button>
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 forcheckboxandmenuitemcheckboxroles 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.
A <button> with role="combobox" needs an aria-expanded attribute, and the validator reports this message when the button doesn't have one.
Custom select menus often use a <button> as a combobox. Clicking it opens a popup, usually an element with role="listbox", where the user picks a value. ARIA requires aria-expanded on every combobox so screen readers can announce whether that popup is open. Start with false in the markup, then have your script set it to true when the list opens and back to false when it closes.
The message also lists type, but type on its own won't clear it: add only type="button" and the validator reports that aria-expanded is required instead. Keep type="button" anyway, since a <button> inside a form submits that form by default.
A button that already has aria-expanded can still trigger this message if it also carries an aria-activedescendant written with no value, as in <button aria-activedescendant role="combobox" aria-expanded="false">. Depending on the order of the attributes, the validator reports this message instead of flagging the empty value. The value of aria-activedescendant has to be the id of the option that currently has focus, so leave the attribute out of the markup until an option is active.
Invalid example
<button role="combobox" aria-controls="sort-options" aria-label="Sort by">
Newest first
</button>
<ul id="sort-options" role="listbox" hidden>
<li id="sort-newest" role="option" aria-selected="true">Newest first</li>
<li id="sort-oldest" role="option" aria-selected="false">Oldest first</li>
</ul>
Valid example
<button type="button" role="combobox" aria-expanded="false" aria-controls="sort-options" aria-label="Sort by">
Newest first
</button>
<ul id="sort-options" role="listbox" hidden>
<li id="sort-newest" role="option" aria-selected="true">Newest first</li>
<li id="sort-oldest" role="option" aria-selected="false">Oldest first</li>
</ul>
An <input type="button"> element requires a non-empty value attribute because that attribute provides the button's visible label and its accessible name.
Unlike <input type="submit">, which defaults to a browser-provided label like "Submit", <input type="button"> has no default label. Without a value, the button renders as an empty rectangle with no text, and assistive technologies have nothing to announce to the user. The W3C validator flags this as an error because the HTML specification mandates that <input type="button"> must have a value attribute, and it must not be empty.
If you need a button without visible text (for example, an icon-only button), use a <button> element instead. The <button> element can contain child elements like <img> or <span>, and you can give it an accessible name through aria-label or visually hidden text. The <input> element cannot contain child content, so value is its only mechanism for labeling.
Invalid example
<input type="button" onclick="doSomething()">
Valid examples
Add a non-empty value attribute:
<input type="button" value="Click me" onclick="doSomething()">
Or switch to a <button> element, which is more flexible:
<button type="button" onclick="doSomething()">Click me</button>
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 Enter or Space — 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 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 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.
An <a> element cannot be placed inside a <button> element because interactive content must not be nested within other interactive content.
The HTML specification forbids nesting clickable elements inside other clickable elements. Both <a> and <button> are interactive content, meaning they each expect to receive user interaction independently. When you nest one inside the other, browsers can't determine which element should handle the click, leading to unpredictable behavior and accessibility problems.
Screen readers and keyboard navigation also struggle with nested interactive elements. A user tabbing through the page may not be able to reach or activate the inner link, or may trigger the wrong action entirely.
To fix this, you have two main options: use a styled <a> element that looks like a button, or use a <button> with JavaScript to handle navigation.
Invalid Example
<button>
<a href="/dashboard">Go to Dashboard</a>
</button>
Fixed Examples
Option 1: Style the link as a button
This is the preferred approach when the purpose is navigation.
<a href="/dashboard" class="btn">Go to Dashboard</a>
<style>
.btn {
display: inline-block;
padding: 8px 16px;
background-color: #007bff;
color: #fff;
text-decoration: none;
border-radius: 4px;
border: none;
cursor: pointer;
}
</style>
Option 2: Use a button with JavaScript
This works when you need button semantics but also want navigation.
<button type="button" onclick="location.href='/dashboard'">
Go to Dashboard
</button>
Option 1 is generally better for navigation because <a> elements communicate the correct intent to assistive technologies and allow standard browser behaviors like right-click "Open in new tab."
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 shouldn't 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. Current versions of the validator report it as a warning ("should not appear"). Older versions reported it as an error ("must not appear").
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>
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.
1K Weekly
1,000 monthly checks. Cancel anytime.
Get 1K Weekly →Join teams across 40+ countries