'Close open tab on click with vanilla javascript

I've been trying to build a tab system where the tabs close on click. At the moment when I try to close an open tab it just remains open.

Basically when I click on a tab the tab opens and when I click on the very same open tab the tab should close. This is what I've been trying to achieve.

Tried using the toggle() method in order to toggle the class .active on and off on the targeted tab like so:

this.classList.toggle("active");

However this doesn't seem to do anything as the clicked tab always remains open.

I believe this is a very simple thing to do but I just can't figure it out.

I'll appreciate some help. Cheers!

const tabs = document.querySelectorAll(".tab");
const tabContents = document.querySelectorAll(".tab-content");

for (let i = 0; i < tabs.length; i++) {
    const tab = tabs[i];
    tab.addEventListener("click", switchClass);
}

function switchClass() {
    for (let i = 0; i < tabs.length; i++) {
        const tab = tabs[i];
        tab.classList.remove("active");
    }

    for (let i = 0; i < tabContents.length; i++) {
        const tabContent = tabContents[i];
        tabContent.classList.remove("show-content");
    }

    this.classList.toggle("active");

    const tabDataAttribute = this.getAttribute("data-content");
    document
        .querySelector(`.tab-content[data-content="${tabDataAttribute}"]`)
        .classList.add("show-content");
}
ul {
    margin: 0;
    padding: 0;
}

.tab {
    display: inline-block;
    border: 1px solid lightgrey;
    padding: 10px;
    cursor: pointer;
}

.active {
    background: lightgrey;
}

.tab-content {
    display: none;
    padding: 10px;
}

.show-content {
    display: block;
    background: lightgray;
}
<ul>
    <li class="tab" data-content="tab1">Tab 1</li>
    <li class="tab" data-content="tab2">Tab 2</li>
    <li class="tab" data-content="tab3">Tab 3</li>
</ul>

<div class="tab-content" data-content="tab1">1. Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>
<div class="tab-content" data-content="tab2">2. Vivamus iaculis est in sapien congue, ac condimentum.</div>
<div class="tab-content" data-content="tab3">3. Phasellus aliquam orci neque, non varius quam gravida vel.</div>


Solution 1:[1]

You can't use .toggle() because the first thing you do in the callback is remove the active class from all elements (which is the correct thing to do), so toggling will just turn it back on (as is the current case).

Instead, you've got to keep track of whether the currently clicked tab is the same as the last clicked tab and act accordingly.

In the code below, I've also combined the loops and am using the .forEach() looping syntax instead of a counting loop as .forEach() is simpler.

And, unless you had some other need for it, you don't need the data-content attribute any longer.

const tabs = Array.from(document.querySelectorAll(".tab"));
const tabContents = document.querySelectorAll(".tab-content");

// Use the Array.forEach() syntax for looping as it eliminates
// the need for setting up and managing indexes.
tabs.forEach(function(tab){
  tab.addEventListener("click", switchClass);
});

let priorActive = null;  // Will keep track of last tab made active

function switchClass() {
  // Loop over the tabs
  tabs.forEach(function(tab, index){
    tab.classList.remove("active");             // Remove active
    tabContents[index].classList.add("hidden"); // Hide content with corresponding index
  });

  // Can't use toggle because prior loop just removed the classes, so toggle
  // will always add them back. Instead, you must explicitly turn on or off
  // based on current situation.
  if(priorActive === this){
    this.classList.remove("active");  
    tabContents[tabs.indexOf(this)].classList.add("hidden");
    priorActive = null;
  } else {
    this.classList.add("active");  
    tabContents[tabs.indexOf(this)].classList.remove("hidden");  
    priorActive = this;
  }
}
ul {
    margin: 0;
    padding: 0;
}

.tab {
    display: inline-block;
    border: 1px solid lightgrey;
    padding: 10px;
    cursor: pointer;
}

.active {
    background: lightgrey;
}

.tab-content {
    background: lightgray;
    padding: 10px;
}

/* Tab content will have this by default  */
.hidden {
  display:none;
}
<ul>
    <li class="tab">Tab 1</li>
    <li class="tab">Tab 2</li>
    <li class="tab">Tab 3</li>
</ul>

<div class="tab-content hidden">1. Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>
<div class="tab-content hidden">2. Vivamus iaculis est in sapien congue, ac condimentum.</div>
<div class="tab-content hidden">3. Phasellus aliquam orci neque, non varius quam gravida vel.</div>

Solution 2:[2]

shorthand way :

you can write actions only inside container

then , seprate your element number (with loop counter) by the event target (this.target) of every clicks..

finally , setAttribute to tab button and their content...

i hope to have helped...

let container = document.getElementById("tab-container"); // take container for scoping actions

container.addEventListener("mousedown", (e) => {
    this.tab = document.getElementsByClassName("tab"); // current tab button , clicked..!
    this.content = document.getElementsByClassName("tab-content"); // content of current tab clicked..
    for (let w = 0; w < this.tab.length; w++) {
        // e.button == 0 is equal to left click by client mouse...
        // e.target take the target and return true
        e.button == 0 ? this.target = this.tab[w].contains(e.target) : false 
        if(this.target){
        this.tab[w].setAttribute("data-tab",true); //set Attr..
        this.content[w].setAttribute("data-content",true); //set Attr..
        }else{
        this.tab[w].removeAttribute("data-tab"); //remove Attr..
        this.content[w].removeAttribute("data-content"); //remove Attr..
        }
    }
})
ul#tab-container{
    margin: 0;
    max-width: 330px;
    background-color: #f3f3f3;
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    padding: 0;
}
.tab{
    display: inline-block;
    cursor: pointer;
    padding: 1rem;
    text-align: center;
}
.tab[data-tab="true"]{
    background-color: #fff;
    text-decoration: underline;
    text-underline-offset: 1rem;
}
.tab-content{
    display:none;
    padding: 2rem;
}
.tab-content[data-content="true"]{
    display:inline-block;
}
<ul id="tab-container">
    <li class="tab" data-tab="true">Tab 1</li>
    <li class="tab">Tab 2</li>
    <li class="tab">Tab 3</li>
</ul>

<div class="tab-content" data-content="true">1. Lorem ipsum dolor...</div>
<div class="tab-content">2. Vivamus iaculis est...</div>
<div class="tab-content">3. Phasellus aliquam orci...</div>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Scott Marcus
Solution 2 farid teymouri