Search topics
Buttons announced as links that Space can't press
A pill that opens a form is built on a link with href="#", so a screen reader calls it a link and Space scrolls the page instead of pressing it.
<a href="#" class="pill"> The problem
Screen reader users are told a control is a link that will take them somewhere, when it opens a form, and keyboard users who press Space scroll the page instead.
A shop's product page shows a linen tote that has sold out. Under the price is a dark green pill, "Email me when it's back". Pressing it opens a small form for an email address, over the page. It is drawn as a button, and it does a button's job: it opens something here, and goes nowhere.
It is built as a link. A keyboard user tabs to the pill and presses Space, the key that presses a button, and the page scrolls down a screen. The pill scrolls out of view, and no form opens. Enter opens it. A screen reader user hears "Email me when it's back, link". A link takes you to another page, so they expect to leave the product, and when they look through the page's buttons for a way to be told about the tote, the pill isn't there: it is listed with the links.
Success Criterion 4.1.2 Name, Role, Value asks that the role of every control can be determined by code, so that assistive technology can say what it is. An a element with an href is a link to the browser, and that is the role it hands to a screen reader. The click handler that opens the form doesn't change the role, and neither does a class that draws a pill. So the role on record is link, while the control does a button's job. The ARIA Authoring Practices put it plainly: the actions buttons perform are "distinctly different from the function of a link", and both the appearance and the role of a widget should match the function it provides.
The keys follow the role. A browser turns Enter on a link into a click, but Space on a link is the key that scrolls the page, so the handler never runs from Space. A native button runs its click handler for both keys. href="#" is there only to make the link focusable and to stop the page reloading, and it adds its own surprises: the menu item "Open link in new tab" opens a copy of the same page, and if the script is slow to load, the press jumps to the top of the page.
The pill works with a mouse and with Enter, so a quick test passes it. The gap shows only to people who rely on what the control is called, or on the key a button answers to.
2 of 10 groups affected
- No vision (affected)
- Low vision (not 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 (not affected)
- Seizures (not affected)
- Without vision A screen reader says "Email me when it's back, link", so the person expects to leave the product page, and finds the control among the page's links rather than its buttons. What it does is not what it was called.
- Limited manipulation A keyboard user presses Space, as for any button, and the page scrolls down a screen instead. The pill is out of view, the form never opened, and only Enter works.
Rule-based scanners such as axe see a link with a name and an address, and report nothing: a link that opens a form is still a valid link. What the control does only shows when someone presses it. EaseWeb's audit lists links that point nowhere (href="#", "#!", "" or a javascript: URL) as a lead for a person to press, but a link to a real address whose script opens a form instead looks just as fine. A quick fix a code assistant may suggest, adding role="button", changes what is announced and leaves Space scrolling the page.
Try it
Press Tab to reach “Email me when it’s back”, then press Space. Then try Enter.
Does Space open the form, or does the page scroll? The panel shows what a screen reader calls the pill: a link or a button.
Broken Can you fix this?
About this bag
A roomy tote in washed linen that softens with every wash. It folds flat into its own pocket, so it goes everywhere.
- Washed linen, 40 by 38 cm
- Inside pocket with a zip
- Long handles that fit over a shoulder
Delivery and returns
Free delivery on orders over $60. Returns are free within 30 days, in the original packaging.
Care
Machine wash cold and hang to dry. A warm iron brings back the crisp look.
Screen reader says:
(Tab to the pill)
The fix
Build the control that acts as a button, or give the link role="button" and press it on Space.
- 1A control that opens, changes or sends something has the button role; a link goes to an address.
- 2Enter and Space each press it once, and Space never scrolls the page.
- 3Keep the look: the pill stays a pill, whichever element it is.
<p class="stock">Sold out</p><a href="#" class="pill notify"> Email me when it's back</a><dialog id="notify" aria-labelledby="notify-title"> …</dialog>A button
<p class="stock">Sold out</p><button type="button" class="pill notify"> Email me when it's back</button><dialog id="notify" aria-labelledby="notify-title"> …</dialog>Why this fix: Use native HTML first
<a href="#" role="button" class="pill notify"> Email me when it's back</a>The a element makes the pill a link, and the script makes it act as a button. A screen reader reports the link, and Space, which presses buttons, scrolls the page instead. The same goes for href="javascript:void(0)" and for a link whose address is real but whose script always cancels it.
A button is announced as a button, and the browser runs its click handler for Enter and for Space, so no key handling is needed and the page never scrolls. type="button" keeps it from sending a form it sits in. The CSS takes away the browser's border and grey face and sets the font, so the pill looks as it did.
role="button" makes a screen reader say "button", which mends the role. But ARIA changes only what is announced: the element is still a link to the browser, and Space still scrolls the page. A screen reader user is now told it is a button, and the key every button answers to does nothing. The second fix above finishes the job by hand; a native button needs none of it.
Both pass. Use the button: it does the same job with less code and nothing to give up, since the browser handles both keys and the role, and there is no fake address to explain. Keep the link with role="button" and the Space handler only where the element can't be changed, and test its keys again whenever the theme or plugin updates.
The look and the role go together. A button can look like a link where the design needs it, but an underlined word is what links usually look like, so it suggests to sighted users that it goes somewhere. That is why the Authoring Practices suggest adjusting the visual design to match what the control does. A pill or a button shape tells everyone it acts.
Shared components are where this usually starts. A Button component that renders an a when it is given href, and a button otherwise, keeps the two apart; one that always renders an a, or is passed href="#" to make it focusable, makes every action a link. A router's link component used for an action has the same problem.
A control that changes something on the server, such as adding to a basket or subscribing, also belongs in a button rather than a link to an address with the action in it: a link can be prefetched, bookmarked or shared, and following it again repeats the action. Put it in a form that is sent with a submit button, or call the server from the button's click handler.
What happens after the press is a separate job. The native dialog opened with showModal() moves focus into the form and back to the button when it closes; see modals that leave keyboard focus behind them for dialogs built another way.
Fixing this with an AI coding assistant? Get this guide as Markdown, or get every guide in your coding agent.
Verify the fix
4 checks, no mouse.
Test with: Keyboard, Screen reader
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared button or link component changes.
Limits
This guide covers a link that does a button's job. The reverse, a button that only takes people to another page, is the same topic seen from the other side, and is meant for a later guide: it is announced as a button, and it can't be opened in a new tab or found in the list of links. For a div or span with a click handler and no role at all, see div buttons that can't be reached with Tab. Links that filter or sort the page in place belong to the topic on links that update the page, and a button with no words to icon-only buttons with no accessible name. Test the complete journey with your target assistive technology.
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.