Photo by rawpixel on Unsplash

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.

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.
Default button: black background, white font. Hover button: grey background, black text.

Basic Code

Chrome's user agent gives blue colour and underline style to a default anchor
<a href="http://www.medium.com">
I'll be button
</a>

Try this code in JSFiddle

1. Pseudo class :visited

Chrome: user agent styling for a :visited anchor is purple font

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:

Anchor has a black background with white font on default state, and grey background with black font on hover

Try this code in JsFiddle

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.

Cursor changes to text selector on hover

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;

}

Try this code in JSFiddle

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:

And <a> element is only keyboard accessible when it has a non-empty href attribute. <a> and <a href=""> should not be used for links.

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:

5. Focus ring on click

Button's are automatically focused on click showing the :focus ring

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:

<a href> can be fired by pressing the enter key. <button> can be fired by pressing the enter key or the space bar #HTML #a11y

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)

<a class="anchor-btn" href="#">I’ll be a button</a>
(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:

Events triggered by space bar fire when key is released. Using Enter key will fire event as soon as you push key down.

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.

Voice over announcement: visited, link, I'll be a button

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

  1. Anchor provides the pseudo-class :visited. Overwrite it's style using :hover
  2. An anchor changes the cursor to text editor on hover. Change the CSS to cursor:default
  3. An anchor is underlined by default. Change the CSS to text-decoration: none
  4. An anchor is not focusable when it doesn't have the attribute href or when it has the attribute name
  5. In some browsers, a button will retain the focus and show the :focus ring after click, an anchor won't.
  6. A button can be triggered with ENTER & SPACE key, an anchor is only triggered with ENTER key.
  7. Anchors are not announced the same way by screen readers. Use the role="button" attribute to change how the element is announced.
  8. Skip all this and use a button

Test the final code in this JsFiddle.net

Photo by Aleksandar Cvetanovic on Unsplash

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:

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

Next Articles

CPACC Certified Accessibility professional, front end development, a11y advocate, casual gamer, sleight of hand rookie.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store