17
How Not to Create a Button
I have a habit of testing websites with a keyboard. I often encounter buttons that don't work as expected. It's frustrating, and I usually start digging into the source code to understand why the button isn't working. These reasons vary - some of them could be described as "normalized" patterns (I disagree), and some are really imaginative.
I'll share some ways to not create a button in this blog post. I'll also explain why those patterns aren't a good idea. You can find instructions on creating a button the right way in the last section, The Right Way to Create a Button.
This div
as a button pattern is by far the most common. Here's a code snippet to demonstrate it:
<div onClick="...">
I pretend to be a button
</div>
Sometimes these elements even have tabindex="0"
to make them focusable with a keyboard. But they never, ever seem to have the keyboard event listeners for space and enter. This means that even if the user can focus on the so-called button, they can't activate it with a keyboard.
Another thing that is often missing is the role attribute to communicate that there is a button. When it's missing, non-sighted screen reader users won't even know any button exists. For them, that particular element is a focusable text they might encounter if they're browsing the page's content.
So, while this pattern works for mouse users, it's excluding most of the other user groups out there. It's possible to make a div
work as a button for all these groups, but it's lots of work and makes the code harder to maintain. And you know, by using the button element, you'd get all that for free.
Another HTML element I often see used as a button is the anchor-element, so, link. In these cases, it's styled to look like a button. However, underneath it all, it's a link:
<a href="...">
I, too, pretend to be a button
</a>
"But call to actions are a thing, right?" you might ask. Yeah, I know, they're a pattern that is widely used. And it's a bit confusing pattern - they often look like a button but then lead to another part of the website. And they don't initiate an action, such as opening a modal.
From the usage point of view, it doesn't make much difference for a sighted mouse user, whether it's a link or button under the hood. I mean, it works when clicking, be it a button or a link.
However, for non-mouse users, it does. For example, keyboard users have certain ways to interact with an element. They can activate a button with space and enter, and link only with enter. This means, that the so-called button is not actionable with space.
And why it is a problem? Well, have you ever pressed space on a site when it's not focused anywhere? It scrolls the page down. And that's what happens when you're focused on a link and press space. It's frustrating as user needs to scroll back up where they were. It might be even painful for some users in cases where every extra keystroke causes pain.
And then there's this: When users don't know that there is a link underneath the button, they don't have the control to use it as they would with a link. This means that they might, for example, want to open the link to a new tab or window.
<a role="button" tabindex="0" onClick={…}>
I'm a link, I'm a button. Who am I?
</a>
This one is my new favorite. I mean, it's a creative way of making a button even more confusing for many. For screen reader users, it indeed seems like a button. It even works if they try to activate it with screen reader commands, as they trigger the click-element. However, this type of button doesn't work with keyboard navigation.
Why, you ask? Isn't it a link, so it works with enter, right? The answer is no. That element is not a link because it no longer has the href
-attribute. Removing the href
-attribute strips away the semantics and keyboard interaction. This way, even the enter-key doesn't work with the element.
I think the most common reason for using this solution is libraries and frameworks hiding away the semantics of an element. My guess for this particular case is that there is some kind of CSS-in-JS-library in use, and the developer has needed the link's styles for a button. Then they have extended the link component and added all the attributes.
Okay, the previous two examples are relatively common ones. The following two are (hopefully) not that popular.
First, I present to you a button element wrapping a link:
<button>
<a href="...">
I'm a link wrapped in a button. And I don't really work
</a>
</button>
First, let's start with the fact that this is incorrect HTML. A link can't be inside a button, as the HTML specification defines the button's content model:
Phrasing content, but there must be no interactive content descendant and no descendant with the tabindex attribute specified.
The button-element in HTML-specification
Even though anchor-tag is phrasing content, it's also interactive, so this rules it out.
This pattern also does not work well for screen reader or keyboard users. Because the link is wrapped with a button, there's an extra tab stop before the user reaches the actual link. And as the button doesn't have a click-handler, trying to activate it doesn't do anything. So, the user feels like they're on a control, but it doesn't work, as the button still gets focused.
That is really frustrating and confusing for the user. If they try to go forward and tab to the link element and then press enter, they'll get the "button" to work. However, it is not clear that there is a next tab stop. And with the link, all the same, problems presented in the previous section remain.
For screen reader users, it presents even more problems. I tested this pattern with VoiceOver and couldn't get it to work at all. I'm not sure about the other screen readers, but I'd guess they also have problems.
This pattern has been the wildest of them all. And at the same time, I understand how a developer has ended up with this pattern. So, in this case, we have a checkbox-input, which is visually hidden, and then the label is styled to look like a button. The code:
<label htmlFor="checkbox">
I also want to be a button
</label>
<input
id="checkbox"
type="checkbox"
checked />
As mentioned, I understand how the developer ended up with this. There's this idea of changing something based on two states. The data that this particular checkbox represents can either be on or off. And based on that state, there are changes in the UI.
However, the checkbox serves another role in the UI. As Heydon Pickering mentions in Inclusive Components, users might suspect that they're also choosing a value for submission. This, however, is true only for screen reader users, as they hear that the underlying component is a checkbox.
The problem for sighted users is that as the label is styled to look like a button, they expect it's a button. But if they try to interact with it using a keyboard, it gets confusing. You see, the key that toggles a checkbox is space. So it doesn't work with enter at all.
So, after all these not-so-great ways of creating a button, let's look into making it the right way.
It's pretty simple. Use the <button>
-element. It has everything out of the box: the role of a button to convey its semantics, tab index to put it into tab order, and easiness of giving it a click handler that handles the keyboard interaction as well.
<button onClick={...}>
I am actual button
</button>
This helps your and your colleagues' lives, as the code is easier to maintain. Also, using semantic elements helps the users - not everyone uses a mouse, so having the expected keyboard interaction is required. You don't want to exclude anyone, right?
- The button-element
- Inclusive Components by Heydon Pickering
17