Search topics
Disabled buttons that can't be found or say why
A Place order button is greyed out until the terms box is ticked, but it is either disabled only by a class, so it is announced as ready, or disabled outright, so Tab skips it and nothing says what is missing.
<button type="submit" disabled>Place order</button> The problem
Keyboard and screen reader users either hear that a greyed-out button is ready when it isn't, or never find it, and nobody is told what they still need to do.
A checkout ends with a box to tick, "I accept the terms", and a Place order button below it. Until the box is ticked, the button is grey. A sighted mouse user may guess that the box is holding it back. Others can't.
On one site the button is grey only because of a class. A screen reader user tabs to it and hears "Place order, button", which sounds ready. A click does nothing, because the class turns pointer events off, but Enter still submits the form, and the order comes back with an error at the top of the page. On another site the button has the disabled attribute. A keyboard user tabs from the checkbox straight to the "Delivery and returns" link below, and never lands on the button at all. A screen reader user moving with Tab never hears that there is a button, and nothing on the page says what is missing.
The same thing happens with any action that waits for something: Next on a step that isn't complete, Save on a form with no changes, Add to basket before a size is chosen, Send on an empty message.
Success criterion 4.1.2 requires that the state of a user interface component can be determined by software. Unavailable is a state. When a button is drawn as unavailable but its code says nothing, assistive technology announces it as an ordinary button, and the keyboard still runs it. That is the failure the criterion names.
A button with the disabled attribute exposes its state, so it passes 4.1.2. It still leaves people stuck. Browsers take it out of the Tab order and draw it faint, and WCAG sets no contrast for disabled controls, so a person may never find the button or read its label. The topic asks for more than the criterion: that the control can be found, and that it says why it is disabled.
The barrier comes at the end of a task, after the person has filled in everything else. A long form on a phone hides the checkbox above the fold, so even a sighted person may see only the grey button.
4 of 10 groups affected
- No vision (affected)
- Low vision (affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (affected)
- Reach, strength (not affected)
- Cognitive (affected)
- Seizures (not affected)
- Limited manipulation Keyboard users either reach a button that ignores the mouse but still submits on Enter, or move straight past a disabled one, so they can't tell whether the form is ready.
- Without vision Screen reader users hear "Place order, button" on a button that is greyed out, or, moving with Tab, never meet the disabled button at all, and the reason is never read to them.
- Limited vision Disabled buttons are drawn faint and need no contrast, so people with low vision may not read the label, or see that it changed when the form became ready.
- Limited language, cognitive and learning abilities A grey button with no reason leaves people to work out which field is holding it back, and some give up on the form.
Axe and similar tools check that a button has a name and a role. A button greyed out by a class has both, so it passes. A disabled button is skipped by contrast checks on purpose, and nothing asks whether a reason is shown. Code assistants usually reach for the disabled attribute, because it fixes the state, and leave the button unreachable with no reason. When they use aria-disabled, they often forget to block the action, so the button announces "dimmed" and still submits. Tab through the form, then press the button before the form is ready, and see what you are told.
Try it
Press Tab to move from the checkbox to Place order, then press Enter before ticking the box.
Can you reach the button, and does anything tell you what is missing? The panel shows what a screen reader announces.
Broken Can you fix this?
Screen reader says:
(Tab through the form)
The fix
Expose the state in code, keep the button where people can find it, and say in text what is still needed.
- 1Never draw a control as disabled without disabled or aria-disabled on the element that takes focus.
- 2Keep a control people need to find in the Tab order: leave it enabled, or use aria-disabled.
- 3Say in visible text what is missing, beside the button or as an error beside its cause.
A button greyed out by a class
<label><input type="checkbox" name="terms"> I accept the terms</label><button type="submit" class="btn is-disabled">Place order</button>A button that stays enabled and explains when pressed
<label><input type="checkbox" name="terms" id="terms" aria-describedby="terms-error"> I accept the terms</label><p id="terms-error" class="error" hidden>Tick the box to accept the terms.</p><button type="submit">Place order</button>Why this fix: Explain instead of disabling
The button looks unavailable, but only the class says so. It is announced as "Place order, button", and pointer-events: none stops the mouse but not the keyboard, so Enter still submits the form.
The button is always reachable, always readable, and always does something. Pressed too early, it shows the error beside the checkbox and moves focus there, where a screen reader reads the label, the invalid state and the error. There is no disabled state to expose, so there is nothing to get wrong.
All three pass 4.1.2. Leave the button enabled and explain when it is pressed: it keeps the button where everyone can find and read it, gives a reason only when someone needs one, and uses the error handling the form needs anyway. Use aria-disabled when the design must show that the button isn't ready yet, accepting that the script, not the browser, has to stop the action. Native disabled with a visible reason is the smallest change to an existing disabled button, but people moving with Tab still pass it by, so treat it as a step on the way rather than the end.
A button that is unavailable for a moment, rather than waiting on the person, is a different case. While an order is being sent, keep the button where it is, mark it aria-disabled="true" and ignore further presses, and say "Placing your order" in a status message. A native disabled there drops focus to the page body in some browsers, and the person loses their place.
Disabled links don't exist in HTML. A link with a disabled class is still a link. When a link must not be followed, remove its href, so it is no longer a link or a Tab stop, or turn it into a button with aria-disabled.
The faded look is where the contrast goes. Disabled controls are exempt from the contrast criteria, but the exemption doesn't make them readable. Draw an unavailable button with readable text and change something besides colour, such as a dashed border. Native disabled buttons are drawn in GrayText in forced colors; a button styled from aria-disabled loses that look unless you add a forced-colors rule.
Several reasons at once need several messages. When Place order waits for a delivery slot and the terms, list both beside the button, or let the enabled button show an error summary of everything that is missing, as in error summaries that list every problem. See form errors that say what went wrong for the error beside each field.
Component libraries often expose a disabled prop that sets the attribute and an option, such as focusableWhenDisabled, that sets aria-disabled instead. Check which one the shared button uses, and which element in it takes focus.
Fixing this with an AI coding assistant? Get this guide as Markdown
Verify the fix
6 checks, no mouse.
Test with: Keyboard, Screen reader, Forced colors, Zoom
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared button component changes.
Limits
This is a starting pattern, not a guarantee for every form. Screen readers word aria-disabled and disabled differently ("dimmed", "unavailable", "disabled"), and some read aria-describedby only after a pause or on request. The error wording and its place on the page depend on the rest of your form. Test the whole journey, from an empty form to a placed order, with the assistive technology your users have.
This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.
Keep this fixed as your code changes.
Turn guides like this into rules your coding agents and CI follow, then have the result retested with a keyboard and screen readers.