Link Anti-patterns

Links are most useful when they are discoverable and have a predictable outcome.

The Problem

When links are disguised in another tag or are improperly formatted, they become harder to find and may have unexpected outcomes.

The Solution

Anything that behaves like a link should use an <a> tag with an href, anything else should use a <button>

Related Articles

Live Examples

Before illustrates the problem, After illustrates the solution. Click the header to see it larger in a modal.

before

after

Code Comparison

Code diff between the before and after examples above to show the changes necessary. To copy the final source click on the 'after' path link before the diff.

source

Comparing /examples/navigation/link-anti-patterns/before/index.html to /examples/navigation/link-anti-patterns/after/index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Accessibility Solutions - Navigation - Link Anti-patterns</title>
<link rel="stylesheet" href="../../../presentation.css" />
<link rel="stylesheet" href="link-anti-patterns.css" />
</head>
<body id="top">
<h1>Links Anti-pattern examples</h1>
<h2>Using Improper Tags</h2>
<p>
We lose discoverability when using a tag other than
<code>&lt;a&gt;</code> for links, they're not in the tabindex nor in the
list of links in VoiceOver Rotor.
</p>
<
span class="link span-linka href="#bottom" onclick="scrollToBottom(event)"
>This is
a span pretending to be a linklink to the bottom of the page</span
>

<h2>Link without an href</h2>
<p>
Links that don't have an href and rely on JavaScript are not fully
discoverable either, they're often removed from the tabindex.
</p>
<a
class="linkhref="#bottom" onclick="scrollToBottom(event)"
>This is a link with
out an href fallback</a
>

<h2>Interactive controls that don't behave like a link</h2>
<p>
The opposite issue as above, when a link has an <code>href="#"</code> or
similar, chances are it'd be better suited as a button.
</p>
<
a class="link" href="#"button onclick="doSomething()">
>This is a linkbutton that should be a does something
</
button</a
>

<h2>Linked image without alt text</h2>
<p>
Without alternative text, a linked image is not fully discoverable either.
</p>
<a href="#bottom" onclick="scrollToBottom(event)">
<img src="/img/logo.svg" width="180" alt="Accessibility Solutions" />
</a>

<div class="super-long-space"></div>
<a href="#top" id="bottom" onclick="scrollToTop(event)"
>Going up? Back to top</a
>

<script src="link-anti-patterns.js"></script>
</body>
</html>

styles

Comparing /examples/navigation/link-anti-patterns/before/link-anti-patterns.css to /examples/navigation/link-anti-patterns/after/link-anti-patterns.css

.link {
color: #dc3542;
font-weight: bold;
transition: all 0.3s ease;
}

.link:hover,
.link:focus {
color: #d21a28;
cursor: pointer;
text-decoration: underline;
}

javascript

Comparing /examples/navigation/link-anti-patterns/before/link-anti-patterns.js to /examples/navigation/link-anti-patterns/after/link-anti-patterns.js

function doSomething() {
alert("did something");
}

function scrollToBottom(e) {
e.preventDefault();
const anchor = document.querySelector(e.currentTarget.getAttribute("href"));
window.scrollTo({ top: document.body.scrollHeightanchor.offsetTop, behavior: "smooth" });
}

function scrollToTop(e) {
e.preventDefault();
window.scrollTo({ top: 0, behavior: "smooth" });
}