When does an anchor becomes a button?
A detailed description on how to convert an anchor into a button.
During my latest project implementation, the front end team grew from two developers to ten in less than a year. As the team grew, many new members faced the same problem over and over again: when trying to develop interaction, some of them used <button>
and some used <a>
.
With some CSS & JS, any developer is capable of styling an element to look like a button. A<div>
or a <span>
can be used, but the most common approach is to use the <a>
element. It's focusable and clickable, so it seems like the best choice for any of these instances.
Within our team, anchors became the norm for icon buttons, tab sets, and other interactive elements that do an action without leaving the page. This included actions such as play video, close model, open menu, back to start, close menu, and load more articles; the choice was left to the developer’s preference. However, there is a lot to consider before you can say an anchor is a button (and maybe it will never be one, but sometimes it gets close).
In this article, we will take a journey through properly creating a button out of an anchor element, including all the implications and code required. If you want to skip all the technical gibberish and just read a to-do list, skip to the summary.
Hypothesis
As an example, let’s pretend we want to create a button that has the following high level requirements:
- The button should be black in its default state.
- The button should be grey on :hover.
- The button should provide a warning in the console when clicked.
Basic Code
<a href="http://www.medium.com">
I'll be button
</a>
1. Pseudo class :visited
The first difference between an anchor and a button has to do with visited links. When using anchors, the browser's user agent applies purple font styling to any link that the user has already visited. It's important to note that the styles defined by the :visited
pseudo-class will be overridden by any subsequent link-related pseudo-class ( :link
,:hover
, :active
) that has at least equal specificity.
Read the :visited definition by MDN
2. Styling :hover
To start this transformation let's add all the styling required to make this anchor look like a button. Add button styling, remove the underline decoration and overwrite the link :visited
pseudo-class using the:hover
selector.
a {
display: inline-block;
background: #000;
color: #FFF;
border: 0;
border-radius: 4px;
text-align: center;
padding: 20px 30px;
line-height: 1;
text-decoration: none;}a:hover {
background: #DDD;
color: #000;
}
The button is looking better now:
Be wary, if the anchor doesn't have the href
attribute, the cursor is changed from a pointer to a text select when you move your mouse slowly over the text. This might not seem much, but it's on the small details that we want to fix.
Let's fix this with one CSS line:
a {
display: inline-block;
background: #000;
color: #FFF;
border: 0;
border-radius: 4px;
text-align: center;
padding: 20px 30px;
line-height: 1;
text-decoration: none; /* Make the cursor be an arrow on hover */
cursor: default;
}
3. Keyboard Focus
It’s important to know that an anchor element is only keyboard accessible when it has a non-emptyhref
attribute, most importantly you should know that an anchor is not focusable when it just have the name
attribute.
<a href='#'>I am focusable</a>
<a name='test'>I am not</a>
<a>I can't be focused either</a>
Test this code in JsFiddle.net
Rian Rietveld writes the following:
To be selectable, with keyboard (TAB or SHIFT + TAB), an empty anchor requires the attribute tabindex="0"
. Never set the index higher than 0, it messes up with the tab ordering on the screen.
<a tabindex='0'>I’m not a button, but I'm focusable</a>
<a>I can't be focused</a>
Test this code in JsFiddle.net
Did you noticed there's a focus ring around the elements when using the TAB key? It's added to indicate the keyboard users (or any super user) what current element has the focus on the page.
Please do not remove this. The use of outline:0
or outline:none
has a major effect upon all keyboard only users regardless of visual impairment. Removing outlines in CSS creates issues for people navigating the web with a keyboard, as stated in all these articles:
- https://developer.mozilla.org/en-US/docs/Web/CSS/outline#Accessibility_concerns
- https://a11yproject.com/posts/never-remove-css-outlines/
- http://www.outlinenone.com/
- https://webaim.org/blog/plague-of-outline-0/
5. Focus ring on click
Another styling difference, introduced by some browsers, comes after a button is clicked. The focus will automatically be set to this element after the mouse is released. And the outline default styling is added.
This is one of the main reasons why some developers use outline:none
or choose <a>
over <button>
, and after you use it once, it just becomes a habit. Maybe the design team didn't account for this. It could be that they are not aware. Maybe we need to communicate this information to our designers & clients to make them aware that this could (and will) happen.
The complexity involved in trying to determine if the element was selected via keyboard or after a click has been described in an article by David Gilbertson; how to remove that ugly :focus ring (and keeping it too), he provides detailed code and instructions.
Another solution to this “problem” is presented by José Perez on his article Hiding the outline or :focus ring in an accessible way
There is a pseudo selector :focus-ring coming up for this use case https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible. However until it's supported by all the browsers, we're stuck with CSS & Javascript solutions.
There's no perfect solution for this problem yet. Do a quick search for suppressing focus outlines or head over to stackoverflow and you'll see the amount of answers that prescribe the usage of * { outline:none }
.
Maybe it’s our fault for not having found a design pattern for this specific event. But it’s something that we need to solve as a community, we need to keep the focus ring while not messing up with the beautiful design.
6. Keyboard Interaction
Button and anchor have automatic interaction with input devices (keyboard, mouse, touchpad, or switches). For instance, a button is clickable by default, so is an anchor. However a <div>
or a <span>
do not have any. To simulate a button, Javascript listeners will have to be added to listen to click events and keyboard events.
When it comes to keyboard interaction, the <button>
element is triggered with both ENTER and SPACE key, the anchor element <a>
is only triggered with the ENTER key.
To make an anchor behave like a button, you'll need to extend your javascript code and add a listener to the SPACE key.
Adrian Roselli’s wrote about this:
So to continue with our example, let's add a selectable an id to the anchor element. (Using id is not ideal for this scenario, but this is just a quick tutorial)
HTML
<a class="anchor-btn" href="#">I’ll be a button</a>
Javascript
(function() {
const KEY_SPACE = 32,
KEY_ENTER = 13;var fakeButtons = document.querySelectorAll('.anchor-btn');// write message in the console
function doAction() {
console.warn('somebody hit the button');
}// Handle Click Events
function handleClick(e) {
// because we're actually not going outside of this URL
e.preventDefault();
doAction();
}// Handle Keyboard Events
function handleKeyboard(e) {
switch(e.keyCode) {
// Listen to Space
case KEY_ENTER:
console.warn('the anchor has been activated with ENTER key');
doAction();
break;
case KEY_SPACE:
console.warn('the anchor has been activated with SPACE key');
doAction();
break;
default:
console.warn('An unexpected key was used to activate this element, Keycode:', e.keyCode);
break;
}
}/**
* Attach Events to elements
* @param {DOM} elem - Dom element
* @param {string|Array} event - Event or events to attach
* @param {function} fn - Function to execute
* @returns {void}
*/
function attachEvents(elem, event, fn) {
if ((event && (typeof event === 'string' || typeof event === 'object')) && (elem) && (fn && typeof fn === 'function')) {
if (typeof event === 'string') {
if (NodeList.prototype.isPrototypeOf(elem)) {
elem.forEach(function (el) {
el.addEventListener(event, fn, false);
});
} else {
elem.addEventListener(event, fn, false);
}
} else {
event.forEach(function (ev) {
if (NodeList.prototype.isPrototypeOf(elem) && typeof ev === 'string') {
elem.forEach(function (el) {
el.addEventListener(ev, fn, false);
});
} else if (typeof ev === 'string') {
elem.addEventListener(ev, fn, false);
}
});
}
}
}// Add Event Listeners
attachEvents(fakeButtons, 'click', handleClick);
attachEvents(fakeButtons, 'keyup', handleKeyboard);
})();
Again another interesting tweet by Adrian:
I like using the keyup event, instead of the keydown, I don’t want to activate the anchor too early. I don’t want to continue triggering the event if the user keeps the key pressed. We could be forgiving and allow the user to move the mouse out of the element before the key is released (not a common case, but it’s nice to provide)
7 . Role=button
Even with all this code, our<a>
is not a <button>
yet. Screen readers will still announce this element as a visited link.
To make it button, it requires the aria role="button"
.
When the button role is used, screen readers announce the element as a button, generally announcing “click” followed by the button’s accessible name. The accessible name is either the content of the element or the value of an aria label or description, if included.
<a class='anchor-btn' href="http://www.medium.com" role='button'>I’ll be a button</a><a class='anchor-btn' tabindex='0' role='button' >I’ll be a button</a>
Interesting fact about roles and types. A button element is by default of type="submit"
. When used inside a form it will execute the specified submit form action; outside of a form, it won't do anything. That's why it’s highly recommended to specify the attribute type="button"
<button type="button">I'm outside a form</button>
The button won't do anything unless listeners are specified for click or keyboard events or by a attribute function onclick="myFunction()"
.
8. One more thing
As pointed out by MarcelloP in the comments, this is known as an anti-pattern. As you can see from this post, trying to disguise an <a>
as a <button>
, is really complex to implement.
If its navigating it can be an
<a>
, otherwise its a<button>
An <a>
will never be a <button>
. Skip all this trouble and use a button.
Summary
- Anchor provides the pseudo-class
:visited
. Overwrite it's style using:hover
- An anchor changes the cursor to text editor on hover. Change the CSS to
cursor:default
- An anchor is underlined by default. Change the CSS to
text-decoration: none
- An anchor is not focusable when it doesn't have the attribute
href
or when it has the attributename
- In some browsers, a button will retain the focus and show the
:focus
ring after click, an anchor won't. - A button can be triggered with
ENTER
&SPACE
key, an anchor is only triggered withENTER
key. - Anchors are not announced the same way by screen readers. Use the
role="button"
attribute to change how the element is announced. - Skip all this and use a button
Test the final code in this JsFiddle.net
Conclusion & Resources
Mozilla Developer’s Network gives a clear definition for anchor and button.
The HTML
<a>
element (or anchor element) creates a hyperlink to other web pages, files, locations within the same page, email addresses, or any other URL.
Anchor definition by MDN web docs
The HTML
<button>
element represents a clickable button, which can be used in forms or anywhere in a document that needs simple, standard button functionality.
Button definition by MDN web docs
When should we use a button or an anchor?. If if it is navigation it could be an anchor, otherwise it should be a button.
There are many nuances that need to be done in order for an <a>
, <div>
or a <span>
to become a <button>
. It’s highly complicated an many things can go wrong. It's also know as an anti-pattern.
If you still don't trust me, because let's face it how am I, Here are some helpful resources:
- WAI-ARIA button design pattern
- https://twitter.com/RianRietveld/status/1064514001814597633 | Rian Rietveld
- Links vs. Buttons in Modern Web Applications | Marcy Sutton
- The Difference Between Anchors, Inputs and Buttons | David Walsh
- Some general tips on when to use an <a href> versus a <button> or <input type=”submit”> | Adrian Roselli
Thank you so much for taking the time to read this article :). I'll be writing more about my findings in #a11y, so feel free to follow for more.
Happy coding!
-Hector