HTML Guides for disabled
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 core problem is that aria-disabled="true" is purely an accessibility hint — it communicates a disabled state to assistive technologies like screen readers, but it has no effect on the actual behavior of the element. When an a element has an href attribute, the browser treats it as a valid hyperlink regardless of any ARIA attributes. Users can still click it, follow it via keyboard navigation, and navigate to its destination. This mismatch between the announced state ("disabled") and actual behavior ("fully functional link") creates a confusing and misleading experience, particularly for users of assistive technologies.
The W3C validator flags this combination because it violates the principle that ARIA states should accurately reflect an element's true interactive state. A link that claims to be disabled but still works undermines user trust and can cause real usability problems.
Why this matters
- Accessibility: Screen readers will announce the link as disabled, but users who activate it will be unexpectedly navigated away. This is disorienting and violates WCAG guidance on predictable behavior.
- Standards compliance: The HTML specification and ARIA in HTML requirements discourage or disallow this combination because it produces an unreliable user experience.
- Browser behavior: No browser will disable a link just because
aria-disabled="true"is present. Thehrefattribute always makes theaelement an active hyperlink.
How to fix it
You have two main approaches depending on your intent:
The link should be active: Remove
aria-disabled="true"and keep thehref. If the link works, don't mark it as disabled.The link should be disabled: Remove the
hrefattribute. Withouthref, theaelement becomes a placeholder link that is not interactive. You can then usearia-disabled="true"to communicate the disabled state,tabindex="-1"to remove it from the keyboard tab order, and CSS to style it as visually disabled. You should also add JavaScript to prevent activation if needed.
Examples
Incorrect
This triggers the validation error because aria-disabled="true" conflicts with the presence of href:
<a href="/dashboard" aria-disabled="true">Go to Dashboard</a>
Correct — Keep the link active
If the link should function normally, simply remove the aria-disabled attribute:
<a href="/dashboard">Go to Dashboard</a>
Correct — Disable the link
If the link should be non-actionable (e.g., a navigation item the user doesn't currently have access to), remove the href attribute and use ARIA and CSS to communicate the disabled state:
<a aria-disabled="true" tabindex="-1" role="link" class="link-disabled">Go to Dashboard</a>
.link-disabled {
color: #6c757d;
cursor: not-allowed;
pointer-events: none;
text-decoration: none;
}
In this approach:
- Removing
hrefensures the link is not actionable by the browser. aria-disabled="true"tells assistive technologies the element is disabled.tabindex="-1"removes the element from the keyboard tab order so users can't Tab to it.role="link"preserves the link semantics so screen readers still identify it as a link (anawithouthrefloses its implicit link role).- The CSS provides a visual indication that the element is disabled, with
pointer-events: nonepreventing mouse clicks andcursor: not-allowedgiving a visual cue on hover.
Correct — Use a button instead
If the "link" triggers an action rather than navigating somewhere, consider using a button element instead. Buttons natively support the disabled attribute:
<button type="button" disabled>Perform Action</button>
This is the simplest and most robust solution when the element doesn't need to be a link. The disabled attribute is natively understood by browsers and assistive technologies without any ARIA workarounds.
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>
disabled is a boolean attribute, so on every element that accepts it you write it bare, empty or as disabled="disabled", and a value like "true" or "false" is an error.
A boolean attribute can only hold the empty string or its own name, in any letter case. So disabled, disabled="", disabled="disabled" and disabled="DISABLED" all pass. The validator reports any other value with this message and quotes what it found: "true", "false", "1", "yes", "disabled " with a trailing space, and also "disabled" with quote marks inside the value. That last one shows up as “"disabled"” and comes from markup like disabled='"disabled"' or disabled=""disabled"", usually a template that wraps an already quoted value in another pair of quotes.
Browsers don't read the value. If the attribute is there, the element is disabled, so disabled="false" disables it just as disabled="true" does. To enable the element, remove the attribute. In JavaScript, set the property (field.disabled = false) or call removeAttribute("disabled"). Calling setAttribute("disabled", "false") leaves the element disabled.
The bad value usually comes from a form builder or template that prints a boolean setting straight into the markup, often with aria-disabled="true" next to it. The validator also warns that aria-disabled is unnecessary on an element that has disabled, so remove it while you're there.
Elements that don't take disabled at all, such as <div> or <a>, get a different error: Attribute “disabled” not allowed on element “div” at this point. Remove the attribute there instead of fixing its value.
What disabled does depends on the element:
<input>,<select>,<textarea>and<button>: the control can't be focused or used, and the browser leaves its name and value out of the submitted form data. It also skips validation, so arequiredfield stops blocking the form while it's disabled. If the user shouldn't edit a field but the server still needs its value, usereadonlyon a text<input>or a<textarea>, since read-only fields are submitted.<select>has noreadonlyattribute, so send its value in a hidden<input>instead.<option>and<optgroup>: a disabled option is greyed out and can't be picked, anddisabledon an<optgroup>disables every option inside it. A common use is the placeholder at the top of a drop-down, such as "Choose a size", markeddisabled selectedso it shows first but can't be chosen again. In arequired<select>, give itvalue=""as well, so the browser treats it as a placeholder and won't submit the form until the user picks a real option.<fieldset>: disables every form control inside it, except the ones in its first<legend>. A checkbox in the legend can therefore switch the rest of the group on and off.<link>:disabledis only allowed together withrel="stylesheet", and the browser doesn't load or apply that stylesheet until the attribute is removed. It's used for alternative stylesheets, such as a high-contrast theme, that a script turns on later withlink.disabled = false.
Invalid example
<form action="/order" method="post">
<label for="size">Size</label>
<select id="size" name="size" required>
<option value="" disabled="true" selected>Choose a size</option>
<option value="s">Small</option>
<option value="m">Medium</option>
<option value="l">Large</option>
</select>
<label for="coupon">Coupon code</label>
<input type="text" id="coupon" name="coupon" disabled="false">
<fieldset id="address" disabled="disabled ">
<legend>
<label>
<input type="checkbox" id="ship-elsewhere">
Ship to a different address
</label>
</legend>
<label for="street">Street</label>
<input type="text" id="street" name="street">
</fieldset>
<button type="submit">Place order</button>
</form>
Valid example
<form action="/order" method="post">
<label for="size">Size</label>
<select id="size" name="size" required>
<option value="" disabled selected>Choose a size</option>
<option value="s">Small</option>
<option value="m">Medium</option>
<option value="l">Large</option>
</select>
<label for="coupon">Coupon code</label>
<input type="text" id="coupon" name="coupon">
<fieldset id="address" disabled>
<legend>
<label>
<input type="checkbox" id="ship-elsewhere">
Ship to a different address
</label>
</legend>
<label for="street">Street</label>
<input type="text" id="street" name="street">
</fieldset>
<button type="submit">Place order</button>
</form>
<script>
document.getElementById("ship-elsewhere").addEventListener("change", (event) => {
document.getElementById("address").disabled = !event.target.checked;
});
</script>
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