Multiple buttons looking like they're staying pressed - one at a time

·

3 min read

In my last post I showed you how to make a button look like it stays pressed when you click on it. But what if you have multiple buttons which you want to have staying pressed when you click them - but only the last one you clicked. If you click one button, all the other buttons should look unpressed. The good news is, it's pretty similar to doing it for just one button.

Setting up the buttons

The HTML here has four buttons, the only difference from when we had one button, is that I numbered them.

<button class="button">Button 1</button>
<button class="button">Button 2</button>
<button class="button">Button 3</button>
<button class="button">Button 4</button>

I added two things to the CSS:

  1. Margins around the buttons so they're not all right next to each other, so we can see them more easily
  2. Made buttons 2 & 4 a different colour so it looks pretty
.button {
  position: relative;
  margin: 0.5em;
  padding: 0.5em 1em;
  border: 0;
  border-radius: 0.5em;
  background-color: pink;
  box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.5);
    cursor: pointer;
}

.button:nth-child(even) {
  background-color: lightblue;
}

.button:active,
.active {
  top: 2px;
  left: 1px;
  box-shadow: none;
}

Adding the JavaScript

This is a little different because we need to select all the buttons and listen out for each one being clicked. You could repeat what we had before four times, but:

  1. You don't want the same code multiple times because if you have to change one you have to remember to change them all
  2. If you need to add a fifth or a sixth button, it's a pain

So we loop through the buttons and listen to each one:

const buttons = document.querySelectorAll('.button');

buttons.forEach(button => {
  button.addEventListener('click', () => {
    button.classList.add('active');
    });
});

Using querySelectorAll here allows us to use forEach. If you haven't seen it before, it's a quick way to loop through all of the buttons. A for loop will work equally well:

const buttons = document.querySelectorAll('.button');

for(let i = 0; i < buttons.length; i++) {
  buttons[i].addEventListener('click', () => {
    buttons[i].classList.add('active');
  })
}

However, if you try this code (whether with the forEach or for loop), you'll notice a problem. If you click Button 1 it stays pressed. If you then click Button 2 then both Button 1 and Button 2 are pressed. What we want is for Button 1 to become unpressed when Button 2 is clicked.

Unpressing buttons

What you could do, is to keep a record of which button was last clicked, and when a different one is clicked, remove the active class from it.

Or when a button is clicked, loop through all the other buttons checking if they have the active class and removing it if it is.

But those are a lot of work. What I do is when a button is clicked, loop through all the buttons and remove the active class, then add it to the button that was clicked. It feels a bit weird to remove the class and immediately add it again (even if it didn't need removing in the first place). But it is the least amount of work.

Technically, since not everything happens at exactly the same time, you might see all the buttons unpressed and then the one you clicked being pressed. But in reality this happens so fast that you're just not going to be able to see it.

In practise, it looks like this:

const buttons = document.querySelectorAll('.button');

buttons.forEach(button => {
  button.addEventListener('click', () => {
    buttons.forEach(button => button.classList.remove('active'));
    button.classList.add('active');
    });
});

It's just one new line (or three if you use a for loop rather than forEach. And that's it, it works!

Here is the CodePen