Buttons vs Links - A Practical Guide for Front-End Developers

Buttons vs. Links: A Practical Guide for Front-End Developers

We may earn an affiliate commission through purchases made from our guides and tutorials.

Use the right element for the right job: links go places; buttons do things. A link (<a href="…">) navigates to a new resource or location; a button (<button>…</button>) triggers an action on the current page (including submitting a form or opening UI). Assistive technologies announce these roles differently, and users form expectations accordingly. Violating those expectations degrades accessibility and UX.

Browsers give you built-in, consistent keyboard support when you use native elements correctly. Links activate with Enter; buttons activate with Space and Enter. Screen readers expose them in different rotor lists (“Links” vs “Buttons”), which users rely on for quick navigation. If you fake a button with a link or vice versa, you must re-implement all of this yourself—and you’ll still surprise users. Prefer semantic elements over ARIA “role” shims.

Good code (navigation):

<a href="https://example.com/other-page">Read the full report</a>

Good code (action on the page):

<button type="button" id="showDetails">Show details</button>
<script>
  document.getElementById('showDetails').addEventListener('click', () => {
    document.getElementById('details').hidden = false;
  });
</script>

Anti-pattern (button used as a link):

<!-- Avoid -->
<button type="button" onclick="location.href='/pricing'">Pricing</button>

Fix by using a real link:

<a href="/pricing">Pricing</a>

Why this matters for accessibility

Correct semantics communicate purpose. Links belong in link lists; buttons belong in button lists in screen readers. Users predict navigation vs. in-place action from that announcement. Breaking this mapping causes confusion and slows task completion. If you must build custom widgets, follow WAI-ARIA Authoring Practices for keyboard interactions and focus handling.

Predictability, context changes, and WCAG

WCAG emphasizes predictable behavior. Changing a page’s context without clear user action is disorienting; opening new tabs or windows is a common culprit. If you open in a new tab, warn the user in the link text or an accessible label; otherwise prefer the default same-tab navigation.

Safer new-tab link (with security):

<a href="https://external.example" target="_blank" rel="noopener"
   aria-label="External resource (opens in a new tab)">External resource</a>

Use rel="noopener" (and often noreferrer) with target="_blank" to prevent reverse-tabnabbing.

SEO and crawlability implications

Search engines discover and evaluate links—specifically <a href="…"> elements with meaningful anchor text. Replacing navigation with JavaScript “buttons” or anchors without href risks broken internal linking, lost link equity, and unreliable crawling. Keep navigational connections as proper anchors; reserve buttons for actions.

Anti-pattern (anchor without href):

<!-- Avoid: not a real link; not focusable by default -->
<a onclick="goto('/features')">Features</a>

Fix:

<a href="/features">Features</a>

Single-page apps: navigation is still navigation

In SPAs, route changes are still navigation. Use your framework’s link component (e.g., React Router’s <Link>/<NavLink>), which renders a real anchor and cooperates with the router/history stack. Manage focus and page titles on route changes so assistive tech perceives the new “page.” After client-side navigation, move focus to a meaningful heading in the new view and update document.title.

Example (React Router):

import { Link } from "react-router-dom";
<Link to="/dashboard">Dashboard</Link>

On navigation:

// After route update:
const h1 = document.querySelector('main h1');
if (h1) { h1.setAttribute('tabindex', '-1'); h1.focus(); }

Forms and buttons

Inside forms, prefer:

<button type="submit">Save</button>
<button type="reset">Reset</button>
<button type="button">Preview</button>

These roles are understood by browsers, assistive tech, and password managers. Use links inside forms only for navigation out of the flow (e.g., “Cancel and return to list” linking elsewhere). (MDN Web Docs)

Minimal ARIA guidance (when you absolutely must)

If business constraints force a non-semantic control, you must meet native parity: focusable (tabindex="0"), correct role (role="button" or role="link"), proper keyboard activation (Space and Enter for buttons; Enter for links), and visible focus styles. This is a fallback, not a first choice.

Example (only when native elements truly can’t be used):

<span role="button" tabindex="0" id="fakeBtn">Add item</span>
<script>
  const el = document.getElementById('fakeBtn');
  el.addEventListener('click', addItem);
  el.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); addItem(); }
  });
</script>

Copy and labeling that set expectations

Write link text that describes the destination (“View pricing”, “Clinical guidelines: sepsis”), and button text that names the action (“Save”, “Add to cart”, “Open filter”). Avoid generic labels like “Learn more” unless the context fully disambiguates the target; descriptive anchors help both users and search engines.

Common anti-patterns and precise fixes

  • Button that navigates
    Problem: <button onclick="window.location='/foo'"> behaves like a link but isn’t announced as one.
    Fix: Use <a href="/foo">…</a>.
  • Link that performs an in-place action
    Problem: <a href="#" onclick="openDialog()"> suggests navigation.
    Fix: Use <button type="button" aria-controls="dialog" aria-expanded="false">Open dialog</button> and update aria-expanded on toggle.
  • Anchor without href
    Problem: Not a real link; not keyboard-focusable; not in link lists.
    Fix: Add href or convert to <button>.
  • Opening new tabs silently
    Problem: Surprising context change; security risk.
    Fix: Announce “opens in a new tab” and add rel="noopener". Prefer same-tab unless there’s a strong reason.

Quick decision rubric you can apply today

  • Will this interaction change the URL or move the user? Use a link.
  • Will this interaction change state or reveal UI here? Use a button.
  • Is this in a SPA? Still a link for route changes; also manage focus and title on navigation.

Developer checklist (ship with confidence)

  • Every navigation affordance is an <a href="…"> with descriptive anchor text.
  • Every in-place action is a <button> (or a native control) that works with Space/Enter, is focusable, and has visible focus.
  • No anchors without href; no divs/spans as interactive controls.
  • New tabs/windows are rare, announced, and use rel="noopener".
  • In SPAs, route changes use real links; focus moves to the new view’s heading; page title updates.

Use this as your single source of truth: if it navigates, make it a link; if it acts, make it a button—and let the platform’s semantics, accessibility APIs, and user expectations work for you.

Was this helpful?

Thanks for your feedback!
Alex is the resident editor and oversees all of the guides published. His past work and experience include Colorlib, Stack Diary, Hostvix, and working with a number of editorial publications. He has been wrangling code and publishing his findings about it since the early 2000s.

Leave a comment

Your email address will not be published. Required fields are marked *