Accessible Link and Button Design Patterns: Choosing the Right Element
Accessible Link and Button Design Patterns: Choosing the Right Element
Links and buttons are the two fundamental interactive elements on the web. They look similar, can be styled identically, and are often used interchangeably — but they have different semantics, different keyboard behaviors, and different expectations from assistive technology users. Using the wrong element creates confusion and breaks interaction patterns that users rely on.
Links vs. Buttons: The Rule
Links navigate. They take the user to a new page, a new section of the current page, or a new resource (file download, external site).
Buttons perform actions. They submit forms, toggle UI states, open dialogs, delete items, or trigger any in-page functionality that does not navigate to a new URL.
This distinction matters because screen readers announce the element type, and users form expectations based on that announcement:
- Hearing “link” → the user expects pressing Enter will navigate somewhere
- Hearing “button” → the user expects pressing Enter or Space will perform an action
When a <button> navigates to a new page or a <a> triggers an in-page action, users become disoriented. Their expected behavior does not match reality.
Keyboard Behavior Differences
| Behavior | <a href> (Link) | <button> (Button) |
|---|---|---|
| Focus | Focusable by default | Focusable by default |
| Activate with Enter | Yes | Yes |
| Activate with Space | No (scrolls page) | Yes |
| Right-click context menu | Link-specific (open in new tab, copy URL) | Standard context menu |
| Middle-click | Opens in new tab | No effect |
| Browser history | Adds to history | No history entry |
| Announce as | ”link" | "button” |
When developers use <a href="#" onclick="doSomething()">, they create an element that announces as a link, has link context menus, but performs a button action. Space does not activate it (it scrolls the page instead). The interaction model is broken.
Writing Descriptive Link Text
Avoid Generic Text
Screen reader users can navigate a page by listing all links. In this context, links are stripped of their surrounding text. A page full of “Click here” or “Read more” links becomes a list of identical, meaningless items.
Inaccessible:
<p>We published our annual report. <a href="/report">Click here</a> to read it.</p>
Accessible:
<p>Read our <a href="/report">2024 Annual Report</a>.</p>
Make Link Text Descriptive of the Destination
The link text should communicate where the link goes or what the user will find:
- “View our pricing plans” rather than “Learn more”
- “Download the accessibility audit (PDF, 2.4 MB)” rather than “Download”
- “Contact our support team” rather than “Click here”
Unique Link Text for Unique Destinations
If two links on the same page go to different destinations, they should have different link text. Two “Learn more” links — one for Product A and one for Product B — are indistinguishable when listed out of context.
When the visual design requires short, repeated link text (like “Read more” on a card grid), use aria-label or aria-labelledby to provide unique accessible names:
<article>
<h3 id="article-title-1">Accessible Forms Guide</h3>
<p>Learn to build forms that work for everyone...</p>
<a href="/forms-guide" aria-labelledby="article-title-1 readmore-1">
<span id="readmore-1">Read more</span>
</a>
</article>
The screen reader announces: “Accessible Forms Guide, Read more, link.”
Button Patterns
Icon Buttons
Buttons with only an icon and no visible text must have an accessible name:
<button aria-label="Close dialog">
<svg aria-hidden="true"><!-- X icon --></svg>
</button>
The aria-label provides the accessible name. The aria-hidden="true" on the SVG prevents screen readers from attempting to announce the SVG content.
Toggle Buttons
Buttons that toggle between states (mute/unmute, expand/collapse, play/pause) must communicate their current state:
<button aria-pressed="false">Mute</button>
<!-- When activated: -->
<button aria-pressed="true">Mute</button>
The aria-pressed attribute communicates the toggle state. Alternatively, update the button label to reflect the state:
<button>Mute notifications</button>
<!-- When activated: -->
<button>Unmute notifications</button>
Both approaches work. Choose consistently across your design system.
Buttons That Look Like Links
Sometimes design requirements call for a button styled as a link (underlined text, no background). The element should still be a <button> if it performs an action. Screen reader users will hear “button” regardless of visual styling, which is correct — the behavior matters more than the appearance.
Links That Look Like Buttons
Navigation elements styled as buttons (large, colorful call-to-action blocks) should remain <a> elements if they navigate. “Sign Up” that navigates to a registration page is a link, even if it is styled as a prominent button.
Focus Indicators
Both links and buttons must have visible focus indicators. The default browser outline works but can be enhanced:
a:focus-visible,
button:focus-visible {
outline: 3px solid #005fcc;
outline-offset: 2px;
}
Use :focus-visible rather than :focus to show focus indicators only for keyboard navigation, not mouse clicks. This addresses the common objection that focus outlines are “ugly” on click while maintaining keyboard accessibility.
WCAG 2.2 Success Criterion 2.4.7 (Focus Visible) requires visible focus at Level AA. Success Criterion 2.4.13 (Focus Appearance) at Level AAA specifies the minimum focus indicator area and contrast.
Common Anti-Patterns
Links With No href
<a onclick="doSomething()">Perform action</a>
Without href, the <a> element is not focusable by default, has no link role, and is not activated by Enter. If it is an action, use <button>. If it is navigation, add the href.
Div or Span as Interactive Element
<div class="link" onclick="navigate('/about')">About Us</div>
No role, no focus, no keyboard activation. See semantic HTML fundamentals for why native elements are essential.
Opening Links in New Tabs Without Warning
target="_blank" opens links in new tabs, which can disorient users, especially screen reader users who may not realize the context has changed. If you must open in a new tab, warn users:
<a href="https://w3.org/wai" target="_blank" rel="noopener">
W3C WAI (opens in new tab)
</a>
Wrapping Large Areas in Links
Wrapping an entire card — heading, image, text, and all — in a single <a> element causes screen readers to read the entire card content as the link name. Instead, use a heading link within the card and extend the click area with CSS:
.card {
position: relative;
}
.card a::after {
content: '';
position: absolute;
inset: 0;
}
This creates a full-card click area while keeping the link text concise.
Testing Links and Buttons
- Tab through all interactive elements: Every link and button must receive focus with a visible indicator.
- Screen reader link list: List all links on the page. Each should have unique, descriptive text.
- Keyboard activation: Links activate with Enter. Buttons activate with Enter and Space.
- Right-click test: Links show “open in new tab” in context menu. Buttons do not.
- Validate with automated testing tools to catch missing labels and role issues.
Key Takeaways
Use <a> for navigation and <button> for actions — never the reverse. Write link text that describes the destination, not generic “click here.” Icon buttons need aria-label. Toggle buttons need aria-pressed or state-reflecting labels. Both need visible focus indicators. The link-or-button decision is a semantic choice, not a styling one — and assistive technology users depend on that choice being correct.