I wanted to create this post because I spent a lot of time searching for a solution to this problem and most of the answers I found in Google/StackOverflow was requiring me to use JQuery. I felt like there has to be a post solving this problem in Vanilla JavaScript for those don't want to use any libraries or frameworks. So here's me trying to achieve that.
First, let’s start with the HTML and create a list of buttons.
<div class="selectSection">
<button type="button" data-number="1" class="active">Button 1</button>
<button type="button" data-number="2">Button 2</button>
<button type="button" data-number="3">Button 3</button>
</div>
I gave the first button a class with the name .active
so it’s clicked when the page is loaded. The data attribute was used to pair each button with the content that’s related to it. I named it data-number
for simplicity. You can use any word & value you like. More info about data attribute.
Now, let’s add the content section and do the same as before with the data attribute:
<div class="contentSection">
<p class="content" data-number="1">Content of button 1</p>
<p class="content" data-number="2"> Content of button 2</p>
<p class="content" data-number="3"> Content of button 3</p>
</div>
As for the CSS, I will only add this part:
.content:not(:first-child) {
display: none;
}
What I did was selecting all classes named .content
and gave every class except the first a display: none
so only the first .content
class will show up. This part of CSS alongside with giving the first button a class of .active
in HTML work together. If you don’t want the first button and content to show up when the page is loaded, delete this part: :not(:first-child)
from the above CSS and remove the class .active
from the first button.
Now let’s get to the last part, JavaScript:
// grab all the buttons
let Buttons = document.querySelectorAll(".selectSection button");
// loop through the buttons using for..of
for (let button of Buttons) {
// listen for a click event
button.addEventListener('click', (e) => {
// Store the event target in a const
const et = e.target;
// select active class
const active = document.querySelector(".active");
/* when a button is clicked, remove the active class
from the button that has it */
if (active) {
active.classList.remove("active");
}
// Add active class to the clicked element
et.classList.add("active");
// select all classes with the name content
let allContent = document.querySelectorAll('.content');
// loop through all content classes
for (let content of allContent) {
/* display the content if the value in the data attribute of the button and content are the same */
if(content.getAttribute('data-number') === button.getAttribute('data-number')) {
content.style.display = "block";
}
// if it's not equal then hide it.
else {
content.style.display = "none";
}
}
});
}
Here is a demo in CodePen.
I hope my explanation was easy to understand and straight to the point. If there's anything that I missed, got wrong or I could've done better, please let me know.
Thank you.
Top comments (4)
Good thing that you used loops! Usually, when the list of buttons is small, I define a function that resets everything, and set active styles on the pressed element.
Something like:
A good hack for me is to put three text elements and hide all of them using css, and when a button gets pressed it will show it's own associated text element.
Your way is still way better than mine.
Loops saved me here. My first solution was using ‘if else’ statement for each one. So the bigger the list the more I write.
I did what you said at first but I added more since its common for the first button to be clicked by default.
Thank you for your reply. 👍
I have found this very useful but I'd like to take it one stage further: as well as hiding the content relating to the other buttons when clicked, I'd like it to hide the content when clicking the open one again. So:
I am going round in circles trying to achieve this with no success.
Hey. I haven't figured a way to do number 3. If I came up with a solution I'll post it.