Anchors vs Buttons
For the longest time, web developers (myself included) have been using <a>
s, <div>
s, <span>
s, and everything but <button>
s to create interactive clickable elements. It would typically go something like this -
<a href="#" role="button" onclick="openMenuModal();">Navigation</a>
A non-actionable or meaningless href
is applied to the anchor element so that the user isn't navigated away from the page, and JavaScript is used to hijack the click event for the link. But there's actually an HTML element for this behaviour - the <button>
, more specifically the <button type=“button”>
, element.
<button type="button" onclick="openMenuModal();">Navigation</button>
Despite this, the anchor element is still used to initiate interactions on a page and in many or most cases, this is simply incorrect. However, the button element is not perfect itself and there are some cases in which I think using the anchor tag is still appropriate. To understand how and why, let's first get into the difference between anchors and buttons.
What’s the Difference? #
Visually, the <a>
and <button type=“button”>
elements can look identical if styled correctly. Semantically, on the other hand, they come with different repercussions, particularly for users who aren't interacting with the element through the simple point-and-click.
The Anchor Element #
The <a>
element represents a hyperlink to a destination page or a section within a page. The element is labelled by its contents, which can be anything from an image to additional (non-interactive) elements like <div>
s, <p>
s, etc.
Because the <a>
element is related to a link, the css pseudo-classes :link
and :visited
are available to it. This is in addition to the user-action pseudo-classes :hover
, :active
and :focus
.
When navigating with a keyboard, the anchor element can be “clicked” by pressing the enter
key. To screen readers, when an anchor tag is clicked, it is expected that the user be navigated away to another page or section.
The Button Element #
The <button>
element, more specifically the <button type=“button”>
element, does nothing. Unlike other button types, such as the <button type=“reset”>
which resets the form it is nested within, the <button type=“button”>
does not have any default behaviour.
Because no link is related to the element, only the default user-action pseudo-classes (:hover
, :active
and :focus
) are available to it. Nowadays, it can be styled as easily and extensively as an anchor element.
When navigating with a keyboard, the button element can be “clicked” by pressing either the enter
or space
keys, the latter being more commonly used. To screen readers, when a button is clicked, it is expected that JavaScript be used to create some interaction on the page. Some screen readers will announce the element as a button, and speech recognition software should enable the button to be clicked by saying "click".
What's the Problem? #
The Problem with Buttons #
First, let me re-iterate the point that if your HTML looks like this ...
<a href="#" role="button" onclick="openMenuModal();">Navigation</a>
...you should probably use a button instead.
However, in some cases, using the button element alone isn't necessarily the most suitable solution either.
The <button type=“button”>
element, as mentioned, by default does nothing. JavaScript must be used to add functionality to it. Therefore, if the user doesn’t have JavaScript enabled on their device, then they can no longer access the content behind the interaction, in this case the navigation menu.
Although we have done right by screen reader users by using semantic markup, we have neglected users who can't/don't enable JavaScript by putting a critical user interaction behind this wall.
The Problem with Anchors #
A common solution to the problem with the button element is to use the anchor element instead, but modify it in two ways -
- Provide a meaningful fallback link in the
href
attribute; and - Alter the semantics of the anchor element so it looks and behaves like a button to screen readers
For example, the anchor-button will look like this -
<a href="#navigation-alternative" role="button" onclick="openMenu();">Navigation</a>
This way, if a user does not have JavaScript enabled they can still access the contents of the modal further down the page. Additionally, because the button element is typically "clicked" with the space
key, we can also bind that event to the anchor-button element.
var bindSpaceKey = function(anchor) {
anchor.addEventListener('keyup', function(event) {
if (event.keyCode == 32) { anchor.click(); }
})
}
var anchorButtons = document.querySelectorAll('a[role="button"]');
for ( var i = 0; i < anchorButtons.length; i++ ) {
bindSpaceKey( anchorButtons[i] );
}
Even though, in my opinion, this solution is better than using a simple button, it isn't perfect either. First, if JavaScript is not enabled then the anchor element should semantically be a link, not a button. But, since we added the button role, it appears to be a button to screen readers even in this case when it is not.
Second, even though we have added the button role, I'm not entirely convinced that this makes the element exactly the same as though it were an actual button. Even though user agents and screen readers should treat it as a button, there is not guarantee that they do.
What's the Solution? #
We have a dilemma.
The
<button>
is semantically correct, but not progressively enhancive, and the<a>
is progressively enhancive but not semantically correct.
Therefore, the only solution I see is to change the element used based on the circumstance. If we are going to have to use JavaScript to bind the space
key event to an <a role="button">
, we might as well go all the way for semantic consistency. When there is no Javascript, the element remains as a regular link semantically. But when there is Javascript, the anchor element is swapped for a button element instead.
Here's how it would work. The element defined in the markup will be a regular link, unmodified.
<a href="#navigation-alternative" class="anchorButton">Navigation</a>
If Javascript is enabled, we can then change all the anchor elements to buttons.
var changeAnchorToButton = function(anchor) {
// Create button element with same content and attributes as anchor
var button = document.createElement('button');
button.setAttribute('type', 'button');
button.innerHTML = anchor.innerHTML;
button.id = anchor.id;
for ( var i = 0; i < anchor.classList.length; i++ ) {
button.classList.add(anchor.classList[i]);
}
// Change anchor element to button element
var anchorParent = anchor.parentNode;
anchorParent.replaceChild(button, anchor);
}
var anchorButtons = document.getElementsByClassName('anchorButton');
for ( var i = 0; i < anchorButtons.length; i++ ) {
changeAnchorToButton( anchorButtons[i] );
}
We can style the .anchorButton
element so both the<a>
and <button>
look the same visually -
.anchorButton {
/* Resets */
font-family: sans-serif;
font-size: 16px;
color: #000;
border: 1px solid #000;
border-radius: 0;
background-color: transparent;
margin: 0;
padding: 0;
height: auto;
width: auto;
cursor: pointer;
/* More styles... */
}
a.anchorButton {
/* Styles for when it's an <a> */
text-decoration: none;
}
button.anchorButton {
/* Styles for when it's a <button> */
-webkit-appearance: none;
-moz-appearance: none;
}
Although this may be a bit radical, it's the only solution I could come up with that works visually, semantically, and accessibility-wise. And, if we style the .anchorButton
element correctly, adding this snippet shouldn't affect the visual look of the page at all.