Button vs Link Accessibility: The Choice That Breaks Keyboards

Links go somewhere and buttons do something, and picking the wrong one quietly locks keyboard and screen reader users out.

By Denis Omerovic ·

A designer hands over a mockup with a blue "Add to cart" pill and an underlined "Show more" link. A developer builds both as whatever is quickest: an <a href="#"> for one, a <div onclick> for the other. The page looks right, and it passes a mouse test. Then a keyboard user tabs straight past the cart, and a screen reader announces "link" for something that never goes anywhere.

Choosing between a button and a link is one of the cheapest accessibility decisions on a site, and one of the most common things an audit flags. This guide covers what each element promises, what breaks when you swap them, how to fix a clickable div, and how to explain the finding to a client or a manager.

What each element promises

The element you choose is a promise about two things: how it behaves with a keyboard, and what a screen reader says about it. The WAI-ARIA Authoring Practices put the difference plainly: "The types of actions performed by buttons are distinctly different from the function of a link" (W3C APG, button pattern).

Link (<a href>) Button (<button>)
What it does Goes somewhere: another page, a section, a file Does something: submits, opens, toggles, adds, deletes
Keys that activate it Enter Enter and Space
Screen reader announces "Link" "Button"
Browser extras Open in new tab, copy address, history, shows in a screen reader's links list Can submit a form, can be disabled

A simple test: if the address bar should change, or the user might want to open it in a new tab, it is a link. If it changes something on the current page, it is a button.

Enter, Space and what breaks when you swap them

The APG link pattern gives a link one key: Enter "executes the link and moves focus to the link target" (W3C APG, link pattern). The button pattern gives a button two: both Space and Enter "activates the button" (W3C APG, button pattern). Native HTML elements do this for you. Swap them, and the behaviour goes with the element, not the design.

HTML
<!-- Broken: announced as a link, Space scrolls the page instead of opening the menu -->
<a href="#" onclick="openMenu(); return false;">Menu</a>

<!-- Fixed: a button that says what it controls -->
<button type="button" aria-expanded="false" aria-controls="site-menu">Menu</button>

Problems with the broken version: a screen reader user hears "link, Menu" and expects to go somewhere. Pressing Space, which people use for buttons, scrolls the page. If the script fails, href="#" jumps to the top. And "open in new tab" opens a copy of the same page.

HTML
<!-- Broken: navigation hidden in a script, no address to open or share -->
<button type="button" onclick="location.href='/pricing'">See pricing</button>

<!-- Fixed: a real link, styled however the design needs -->
<a href="/pricing" class="button">See pricing</a>

This one is less harmful for keyboards, because the button still works, but it drops out of the screen reader's list of links, cannot be opened in a new tab and is not a link search engines can follow: Google says it "can follow your links only if they use proper <a> HTML element with an href attribute" (Google Search Central).

An <a> with no href is not a link at all. The HTML standard says that without an href "the element represents a placeholder link" (HTML Living Standard), and browsers do not put it in the tab order. Click handlers on it work with a mouse and nothing else.

Fixing a div with an onclick

The most common version of this failure is a div or span with a click handler. It looks like a control and acts like one with a mouse, but a keyboard cannot reach it and a screen reader announces it as plain text. That fails WCAG 2.1.1 Keyboard, which requires that "all functionality of the content is operable through a keyboard interface", and usually WCAG 4.1.2 Name, Role, Value, because nothing tells assistive technology it is a control.

HTML
<!-- Broken: no focus, no role, no keyboard activation -->
<div class="add-to-cart" onclick="addToCart(42)">Add to cart</div>

<!-- Fixed: a native button gets focus, Enter, Space and its role for free -->
<button type="button" class="add-to-cart" onclick="addToCart(42)">Add to cart</button>

Two details catch people out:

  • Set type="button". Inside a form, a button with no type is a submit button. MDN warns that a button not meant to submit should have type="button", "Otherwise, they will try to submit form data" (MDN, the button element).
  • Reset the styles, not the element. Developers often avoid <button> because of its default look. A few lines of CSS remove that:
CSS
/* Fixed: a button that looks like plain text, with focus still visible */
.button-reset {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

.button-reset:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

Why role="button" plus tabindex is the last resort

You can make a div behave like a button with ARIA, but you have to rebuild everything the browser would have given you:

HTML
<!-- Works, but only with the script below. Use a real button if you can. -->
<div role="button" tabindex="0" class="add-to-cart">Add to cart</div>
JavaScript
// Every one of these lines is something <button> already does
el.addEventListener("click", addToCart);
el.addEventListener("keydown", (event) => {
  if (event.key === "Enter") addToCart();
  if (event.key === " ") event.preventDefault(); // stop the page scrolling
});
el.addEventListener("keyup", (event) => {
  if (event.key === " ") addToCart();
});

And you still have no disabled state, no form submission, and a component that breaks the first time someone edits the markup and drops the script. The W3C's guidance on ARIA says the same as its first rule: if a native HTML element already has the semantics and behaviour you need, use it instead of adding a role to something else (Using ARIA, first rule). Keep this pattern for markup you genuinely cannot change, such as a third-party widget.

Design and semantics are separate decisions. A "Start free trial" call to action that goes to a signup page is a link, even if it is a big coloured pill. Give it a .button class and keep the <a href>. A "Show more" control that expands text on the same page is a button, even if it is styled as underlined text.

The APG notes that a link-styled element acting as a button can take role="button", but says "a better solution is to adjust the visual design so it matches the function and ARIA role" (W3C APG, button pattern). In practice that means: pick the element by what it does, then style it, and keep a visible focus style on both.

Whichever you choose, give it a name that makes sense on its own. An icon-only button needs an accessible name, and a link's text should say where it goes. Our guide to ARIA attributes covers when ARIA helps and when it gets in the way, and you can check a page for links that do not say where they go.

What an audit flags and how to explain it to a client

Automated tools find the clearest cases: elements with click handlers that are not controls, links with no href, and controls with no accessible name. Our free keyboard and focus checker looks for clickable divs and spans that a keyboard cannot reach, along with removed focus styles. What a tool cannot judge is intent: whether a real <a href> that runs a script should have been a button. That takes a person, so pair the scan with a keyboard test by hand.

When you report it, skip the jargon. Something like this works for a manager or a client:

"Some of the controls on the site, including the add to cart button and the menu toggle, only work with a mouse. People who use a keyboard or a screen reader cannot use them, and so cannot buy. The fix is to build them with the standard HTML button element, which takes a developer minutes per control and does not change how the site looks."

That framing is accurate and gives the person paying for the work a reason to prioritise it: the broken controls tend to sit on the pages that matter most, such as navigation, checkout and forms.

Checklist

  1. Run a free scan on your home page, a product or service page and the checkout or contact form.
  2. Replace every div or span with a click handler with a <button type="button">.
  3. Replace every <a> with no href, or with href="#" and a script, with a button.
  4. Replace every button that only changes the page address with an <a href>.
  5. Give icon-only buttons and links an accessible name.
  6. Tab through the page: every control should be reachable, show visible focus, and work with Enter (and Space, for buttons).
  7. Keep role="button" with tabindex="0" only for markup you cannot change, with the keyboard script in place.

For the wider picture, our guide to keyboard navigation covers focus order, skip links and keyboard traps.

Check a client site for this

Audit any page against WCAG 2.2 AA for free and see every finding in about 30 seconds.

Run a free audit