AccessScanRun a free scan

Guide

Accessible Buttons and Links: Buttons vs Links, Icon Names, and Keyboard Behaviour

Accessible buttons and links start with one decision developers get wrong constantly: which element to reach for. A <button> does something on the current page (submit, open a dialog, toggle a menu). An <a href> navigates somewhere. Pick the wrong one and you break expectations for keyboard users, screen reader users, and browser features everyone relies on, like open-in-new-tab and the back button. Get it right and the platform hands you focus, keyboard activation, and the correct announced role for free.

This guide covers the four things that cause the most real-world failures: choosing button vs link, giving icon-only controls an accessible name, why a <div> dressed as a button is a trap, and the keyboard behaviour you inherit (or have to rebuild). It maps to WCAG 2.2 Level A and AA, the baseline referenced by EN 301 549 and enforced by the European Accessibility Act from 28 June 2025. Want to find these problems on your own site first? Run a free scan with AccessScan.

Buttons vs links: the one rule that settles most arguments

Ask one question: does activating this control change the URL? If the answer is yes, it is a link. If it performs an action and the page stays put, it is a button. "Add to cart", "Open filters", "Play video", and "Submit" are buttons. "View product", "Go to checkout", and "Read the docs" are links.

This is not pedantry. The two elements behave differently in ways users depend on. A link responds to Enter; a button responds to both Enter and Space. A link exposes a context menu with "Open in new tab" and "Copy link address"; a button does not, and should not, because there is no destination to copy. Screen readers announce "link" or "button" so the user knows whether they are about to move or to act.

The common anti-pattern is the link with href="#" or href="javascript:void(0)" wired to a click handler that does something on the page. It announces as a link, so users expect navigation, then nothing navigates, and middle-clicking opens a useless blank tab. If it acts on the page, use <button type="button">. If it goes somewhere, use a real <a href> with a real URL, even in a single-page app where the router intercepts the click.

  • Use `<button>` for: submit, toggles, opening modals and menus, anything driven by JavaScript on the current page.
  • Use `<a href>` for: any navigation, including in-page anchors and client-side routes. The href must be a usable URL.
  • Set `type` explicitly. A <button> inside a <form> defaults to type="submit". Use type="button" for anything that is not submitting the form, or you will trigger accidental submissions.

Never use a div as a button

A <div onclick> or <span onclick> looks identical to a real button once you style it. It is not. A generic element has no role, is not in the tab order, and does not respond to the keyboard. To a screen reader it is silent or announced as plain text, and a keyboard-only user cannot reach or fire it at all. This is a direct failure of WCAG 2.1.1 Keyboard (Level A) and 4.1.2 Name, Role, Value (Level A).

Developers who know this sometimes try to retrofit a div into a button, and it is instructive to see how much you have to bolt on to match what <button> gives you out of the box:

<div role="button" tabindex="0" @keydown="..."> plus a handler that fires on both Enter and Space, plus aria-disabled handling, plus aria-pressed if it toggles. You are reimplementing the browser, badly, and you still lose form participation and the high-contrast-mode styling real buttons get.

The fix is almost always to delete the wrapper and use <button type="button">. Reset the default styling with appearance: none; background: none; border: none; and style from there. You keep the role, the tab stop, Enter and Space activation, the disabled semantics, and focusability, all without writing a line of JavaScript. The same logic applies to React, Vue, and every framework: render a real <button> or <a>, not a clickable <div>. When you genuinely cannot use a native element, you take on the full custom-widget keyboard contract described below.

Accessible names for icon-only controls

An icon button with no text, a hamburger menu, a close X, a search magnifier, has no accessible name. Screen readers announce it as just "button", which tells the user nothing. WCAG 2.4.4 Link Purpose (In Context) (Level A) and 4.1.2 Name, Role, Value (Level A) require that every control communicates what it does.

Give it a name with aria-label, and make sure the visible icon itself is hidden from assistive tech so it is not announced twice or as a meaningless graphic:

<button type="button" aria-label="Close dialog"><svg aria-hidden="true" focusable="false">...</svg></button>

Two details people miss. First, aria-hidden="true" and focusable="false" on the inline <svg> stop the icon from being announced and stop it grabbing a tab stop in older browsers. Second, if there is visible text next to the icon, do not add a redundant aria-label, let the visible text be the name, because a mismatch between visible label and accessible name can also fail 2.5.3 Label in Name (Level A).

  • Name the action, not the icon. "Search", not "magnifying glass". "Menu", not "three lines".
  • Mind the target size. WCAG 2.2 added 2.5.8 Target Size (Minimum) (Level AA): interactive targets should be at least 24 by 24 CSS pixels (with spacing and other exceptions). Icon buttons are the most frequent offender, give them padding.
  • Tooltips are not enough. A title attribute is unreliable for screen readers and invisible to touch and keyboard users until hover, so it is not a substitute for a proper name. If you do add a tooltip, it must obey 1.4.13 Content on Hover or Focus (Level AA).

Keyboard behaviour you inherit, and what you must rebuild

The strongest argument for native elements is the keyboard behaviour you get without any code. A <button> is automatically in the tab order, shows a focus ring, and fires its click handler on both Enter and Space. An <a href> is in the tab order, shows a focus ring, and activates on Enter. None of this exists on a <div> until you write it.

When you must build a custom composite control, a tab list, a menu button, a toggle, you take on the matching keyboard contract from the ARIA Authoring Practices Guide. A menu button typically opens on Enter, Space, and Down Arrow; arrow keys move between items; Escape closes it and returns focus to the trigger. Skipping any of these is a 2.1.1 Keyboard failure even if the mouse works perfectly.

Whatever you build, focus must stay visible. Do not write the blanket *:focus { outline: none; } reset, a common audit failure, it strips the indicator keyboard users rely on. Use :focus-visible so the ring appears for keyboard interaction but not mouse clicks, and give it a 3:1 contrast ratio against its background, the threshold WCAG 1.4.11 Non-text Contrast (Level AA) sets for UI components. You can verify those values with our contrast checker.

  • Tab and Shift+Tab reach native buttons and links automatically; custom widgets need tabindex="0" on the focusable element.
  • Enter activates links and buttons; Space activates buttons (and scrolls the page if focus is not on a control, so prevent default carefully in custom widgets).
  • Disabled state: a native <button disabled> is removed from the tab order and ignores clicks for free. If you use aria-disabled="true" to keep it focusable, you must block the action in your handler yourself.

A quick audit you can run today

You do not need special tooling to catch the worst offenders. Put the mouse aside and Tab through your page. Every interactive thing should receive focus, show a visible ring, and activate with Enter (and Space for buttons). Anything you can click but cannot reach with Tab is almost certainly a <div> masquerading as a control.

Then turn on a screen reader, VoiceOver on macOS or NVDA on Windows, and listen as you move through your buttons and links. Each should announce a meaningful name and the correct role. "Button" with no name, or "link" on something that does not navigate, are the two failures this guide is built to eliminate. For a structured pass across the whole site, a free AccessScan will surface missing names, empty links, and unlabeled controls at scale.

Check your site against AccessScan

See your issues ranked by impact in seconds — free.

Run a free accessibility scan

FAQ

When should I use a button instead of a link?

Use a <button> when activating the control performs an action on the current page, such as submitting a form, opening a dialog, or toggling a menu. Use an <a href> when it navigates to a URL, including in-page anchors and client-side routes. The quick test: if the URL changes, it is a link; if the page stays and something happens, it is a button.

Why can't I just use a div with an onclick handler?

A <div> has no role, is not in the tab order, and ignores Enter and Space, so keyboard-only and screen reader users cannot reach or operate it. That fails WCAG 2.1.1 Keyboard and 4.1.2 Name, Role, Value, both Level A. A native <button> gives you focusability, keyboard activation, the announced role, and disabled semantics for free. Reset its styling with appearance: none instead of building a fake button.

How do I give an icon-only button an accessible name?

Add aria-label to the <button> describing the action (for example, aria-label="Close dialog"), and put aria-hidden="true" and focusable="false" on the inline <svg> so the icon is not announced separately. Name the action, not the shape: "Search", not "magnifying glass". If visible text already sits beside the icon, let that be the name rather than adding a conflicting aria-label.

What keyboard behaviour do native buttons and links provide automatically?

A <button> is in the tab order, shows a focus ring, and activates on both Enter and Space. An <a href> is in the tab order, shows a focus ring, and activates on Enter. A native disabled button is also removed from the tab order automatically. A <div> provides none of this, which is why you should use native elements unless you are prepared to rebuild the entire ARIA keyboard contract.

Are accessible buttons and links legally required?

In practice, yes. Correct names, roles, and keyboard operability are covered by WCAG criteria including 2.1.1 Keyboard, 2.4.4 Link Purpose (In Context), and 4.1.2 Name, Role, Value, all Level A. The WCAG 2.2 Level A and AA baseline is referenced through EN 301 549 and applied by the European Accessibility Act from 28 June 2025. In the US, the ADA names no specific WCAG version (2.1 and 2.2 AA are the de-facto bar), while Section 508 (Revised) and Ontario's AODA are tied to WCAG 2.0 AA. The underlying button-and-link requirements are essentially identical across all of them.

More guides