When Is A Button Not A Button?
When Is A Button Not A Button?
Vadim Makeev2019-02-25T12:00:21+01:002019-02-25T21:04:37+00:00
Let’s say you have a part of an interface that the user clicks and something happens. Sounds like a button to me, but let’s call it a “clicky thing” for now. I know, you’re confident that it’s a button too: It’s rounded and stands out with a nice tomato color, asking to be interacted with. But let’s think about it for a moment. It’ll save time in the long run, I promise.
What if the text in this clicky thing was “Read more”, and clicking it led the user to an article on another page? Hmm. And what if there was a blue underlined word, “Close”, that closes the popup dialog? Is it a link just because it’s blue and underlined? Of course not.
Whoa! It seems like there’s no way to tell if it’s a link or a button just by looking at it. That’s crazy! We need to understand what this thing does before choosing the right element. But what if we don’t know what it does just yet or are simply confused? Well, there’s a handy flow chart for us:
- It’s a button.
- If not, then it’s a link.
- That’s it.
So, is everything a button? No, but you can always start with a button for almost any element that can be clicked or interacted with in a similar way. And if it’s lacking something, like navigation to another page, use a link instead. And no, a pointer is not a reason to make it . We have
cursor: pointer
for that.
All right, it’s a button — we agree on that. Let’s put it in our template and style it according to the design: some padding, rounding, a tomato fill, white text, and even some focus styles. Oh, that’s so nice of you.
<button type="button" class="button">
Something
</button>
<style>
.button {
display: inline-block;
padding: 10px 20px;
border-radius: 20px;
background-color: tomato;
color: white;
}
.button:focus {
outline: none;
box-shadow: 0 0 0 5px #006AE3;
}
</style>
That didn’t take long. You wanted to build it quickly and grab some lunch because you’re hungry. Ok, let’s see how it looks and get going.
Oh my god! Something is wrong with the browser. Why is this button so ugly? The text is tiny, even though we have explicitly set the body
to 16px
, and even the font-family
is wrong. The rounded border with a silly pseudo-shadow is so retro that it’s not even a trend yet.
Ahh, it’s the browser’s default styling. You need to carefully undo it or even add Normalize.css or Reset.css… or you could just use a
What’s the difference between a and a
is an interactive element, meaning that it can be interacted with. That’s deep. You can click it, you can focus on it using a keyboard, and it also conveys an accessible button
role to screen readers, making it possible for users to understand that it’s a button.
Impressive! You’re not only aware of HTML’s element, but you also know a thing or two about ARIA and screen-reader support. You might have even tried VoiceOver or NVDA to test how accessible your interfaces are.
So, you’ve decided to do a trick. You won’t mess with the browser’s styling, and you’ll make the element look like a proper interactive button for users who might need it. That’s smart!
<div class="button" tabindex="0" role="button">
Something
</div>
Now it not only looks right, but it’s focusable via the keyboard thanks to the tabindex="0"
attribute, and screen readers will treat it as a proper button because you have wisely added role="button"
to it. Git commit && push then! There are some additional tasks for this thing, but we’re done with the styling. What could possibly go wrong? Time for lunch. Great, let’s go!
An hour later…
That was a nice lunch! Let’s get back to our clicky thing. We need to complete some tasks before moving on. Let’s see… We need to call a doSomething
function once the button is clicked, and there should be a way to disable the button so that it’s not clickable. Sounds easy. Let’s add an event listener to this button:
<script>
const buttons = document.querySelectorAll('.button');
[...buttons].forEach(button => {
button.addEventListener('click', doSomething);
});
function doSomething() {
console.log('Something!');
}
</script>
Done. The user can now click it with a mouse on the desktop and tap it with a finger on a touchscreen. A click event will fire reliably, and you’ll see a lot of Something! in your console. What’s the next task?
Hold on! We need to make sure it works the same for keyboard users. Because we have this tabindex="0"
on the button, it can be focused, and once it’s focused, users should be able to press the space bar or “Enter” key to trigger whatever we have attached.
So, we need to attach another event listener to catch all keyups, and we’ll trigger our function only for certain keys. Thank God that touch devices are smart enough to convert all taps into clicks; otherwise, we’d have to attach a bunch of touch events, too.
<script>
const buttons = document.querySelectorAll('.button');
[...buttons].forEach(button => {
button.addEventListener('click', doSomething);
button.addEventListener('keyup', (event) => {
if (event.key == 'Enter' || event.key == ' ') {
doSomething();
}
});
});
function doSomething() {
console.log('Something!');
}
</script>
Phew! Now our clicky thing is fully accessible from the keyboard. I’m so proud of you! And JavaScript is truly magical — what would we do without it?
All right, what’s the last task: “The button should have a disabled state that changes its look and behavior to something numb.” Numb? I guess that means something gray and not responsive to interaction. OK, let’s add a state in the style sheet using BEM naming.
<div class="button button--disabled" tabindex="0" role="button">
Something
</div>
<style>
.button--disabled {
background-color: #9B9B9B;
}
</style>
That looks comfortably numb to me. Whenever the button needs to be disabled, we’ll add the button--disabled
modifier to make it gray. But it’s not numb enough yet: It can still be focused and triggered both by a pointer and from the keyboard.
Darn, this is getting tricky.
Not only that, but the button shouldn’t be accessible in the tab order, meaning that the tabindex
attribute should not be there. And we need to check whether the button has the disabled state and then stop triggering our function. Also, this modifier could be applied dynamically. While it’s not a problem for CSS to match elements with selectors on the fly and apply styles, we might need some sort of mutation observer to trigger other changes for this button.
I know, right? We thought this would be a simple little button that triggers a function and has a disabled state. We’ve tried to make it right with accessibility and all that stuff, and now we’re deep in this rabbit hole.
Let’s grab some takeaway food. We won’t be home for dinner by the time we finish and properly test this. Bloody W3C! Why don’t they try to make our lives easier? As if they care about us!
As the matter of fact, they do…
Let’s take a few steps back before jumping into this mess. Why don’t we try to do these things using the element? It’s got some useful tricks up its sleeve, not just the browser’s ugly styles. Oh, and don’t forget
type="button"
— you don’t want the popup’s “Close” button to accidentally submit the form, because type="submit"
is the default value.
Apparently, when the is focused and the space bar or “Enter” key is pressed, it will trigger the
click
event, just as mobile devices do when they get taps, pats, licks or whatever else they’re capable of receiving today. One event listener fewer in our code! Nice.
// A click is enough!
button.addEventListener('click', doSomething);
As for the disabled state, the disabled
attribute is available for the element, as well as for all form elements, including