JavaScript Tutorials

Create A Simple Accordion Menu Using Vanilla JavaScript

Last modified on September 3rd, 2023
Raja Tamil
Javascript

By the end of this tutorial, you’re going to be building a simple accordion menu, just using vanilla javascript of course, with the combination of HTML and CSS.

What are we building?

Accordion Menu HTML Structure

The HTML code of the Accordion Menu is pretty straightforward.

I have a simple section element with an id called #accordion-container.

Inside that, add three divs with the class name .menu and each contains two HTML elements: .header and .content.

The header element also contains .title and .icon elements.

<section id="accordion">

    <div class="menu">
        <div class="header">
            <div class="title">Title 1</div>
            <span class="icon">+</span>
        </div>
        <div class="content">
            Content 1
        </div>
    </div>

    <div class="menu">
        <div class="header">
            <div class="title">Title 2</div>
            <span class="icon">+</span>
        </div>
        <div class="content">
            Content 2
        </div>
    </div>

    <div class="menu">
        <div class="header">
            <div class="title">Title 3</div>
            <span class="icon">+</span>
        </div>
        <div class="content">
            Content 3
        </div>
    </div>

</section>

Accordion Menu CSS

Let’s add some CSS to make to match the final result.

Define a CSS rule for #accordion with a max-width to 500px and make it center to the screen horizontally using margin: 0 auto property.

After that, add borders all-around to each .menu item which is the clickable area of the accordion menu.

#accordion {
  max-width: 500px;
  margin:0 auto;
}

.menu {
  border:1px solid #008080;
  border-bottom:none;
}

 .menu:last-child {
  border-bottom:1px solid #808080;
}

Using flexbox, lets move the “+” icon to the right and keep the title on the left.

Recommended
Must-Know CSS Flexbox Responsive Multi-Column Layout Explained


.header {
  display:flex;
  padding:10px; 
  cursor: pointer;
}

.title {
  flex:1;
}

.icon {
  width:20px;
} 

 .content {
  padding:10px;
} 

Finally, hide the .content by default and I’ll show you how to toggle it’s visibility using JavaScript in just a moment.

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

That looks much better.

Now let’s move into the fun part.

Accordion Menu JavaScript

The first step is to get all the DOM references of the header as well as the content and assign them to the constant arrays called headers and contents respectively.

const headers = document.getElementsByClassName("header"),
      contents = document.getElementsByClassName("content");

Secondly, iterate through the headers array using for loop and attach a click event to each header inside with a callback arrow function.

Recommended
JavaScript For Loop Click Event ← Issues & Solutions Explained

for (let i = 0; i < headers.length; i++) {
     headers[i].addEventListener("click", () => {
     });
}

Finally, toggle the content visibility by using the display CSS property on it. I used the ternary operator here to achieve this but you can also use the regular if statement.

contents[i].style.display = contents[i].style.display == "block" ? "none" : "block";

This works great, but the icon is not changing when the menu opens or collapses, so let’s fix that next.

Accordion Collapsable Icon

Similar to what I’ve done toggling the visibility of the content, show the “+” icon by default and replace it with “-” icon when the accordion menu opens otherwise replace it with the “+” sign when it collapses.

Pretty straightforward.

To do that, get the DOM references of all the icons and assign them to a constant array called icons.

 const headers = document.getElementsByClassName("header"),
        contents = document.getElementsByClassName("content"),
        icons = document.getElementsByClassName("icon");

After that, include the following line inside the click event.

 for (let i = 0; i < headers.length; i++) {
        headers[i].addEventListener("click", () => {
            contents[i].style.display = contents[i].style.display == "block" ? "none" : "block";
            icons[i].innerHTML = contents[i].style.display == "block" ? "-" : "+";
        });
    }

Open One Accordion Menu At A Time

What if you want to open only the clicked accordion menu item and have the others collapse.

To do that, iterate through the contents array inside the click event and check to see if the index value of the headers array matches the index value of the contents array which is i == j.

Now, move the two lines of code inside the condition block, but this time with the incrementor j instead of i.

In the else statement, collapse all other accordion menus using the display none CSS property, as well as reset the default icon to  “+”.

for (let i = 0; i < headers.length; i++) {
        headers[i].addEventListener("click", () => {
            for (let j = 0; j < contents.length; j++) {
                if (i == j) {
                    contents[j].style.display = contents[j].style.display == "block" ? "none" : "block";
                    icons[j].innerHTML = contents[j].style.display == "block" ? "-" : "+";
                } else {
                    contents[j].style.display = "none";
                    icons[j].innerHTML = "+";
                }
            }
        });
    }

Accordion Menu Transition

At this stage, the accordion menu opens and collapses without any type of transition. 

Let’s change that.

Replace the .content CSS rule with the code below. As you can see, I got rid of the display: none and I used the height and overflow properties to hide the content element by default.

.content {
  max-height: 0;
  transition: max-height 1s ease-out;
  overflow: hidden;
  padding:0;
}

Then, create another CSS rule called .content-transition.

.content-transition {
  max-height: 1500px;
  transition: max-height 3s ease-in;
} 

Toggle the .content-transition class if one of the accordion menu items is clicked and remove the .content-transition class from other elements.

for (let i = 0; i < headers.length; i++) {
        headers[i].addEventListener("click", () => {

            for (let j = 0; j < contents.length; j++) {
                if (i == j) {
                    contents[j].classList.toggle("content-transition");
 
                } else {
                    contents[j].classList.remove("content-transition");
                }
            }

        });
    }

As you can see, at this stage the collapsible icon no longer works as expected. That’s because previously I used the display CSS property to hide and show the content element. That is no longer the case as I got rid of the display: none property from the content CSS rule.

Instead, check to see if the height of the content is higher than 0.

 for (let i = 0; i < headers.length; i++) {
        headers[i].addEventListener("click", () => {

            for (let j = 0; j < contents.length; j++) {
                if (i == j) {
                    icons[j].innerHTML = contents[j].getBoundingClientRect().height === 0 ? "-" : "+";
                    contents[j].classList.toggle("content-transition");
                } else {
                    icons[j].innerHTML = "+";
                    contents[j].classList.remove("content-transition");
                }
            }

        });
    }

I removed the padding from the .content class in order to have a more smooth transition and the workaround is to add another HTML element, such as p tag inside .content, and put all the code inside.

<div class="content">
   <p>
      Content 1
   </p>
</div>
...

As you can see, there is no space between the content and the border as I no longer have the padding to the .content due to the overflow issue.

Instead, wrap the actual content with the p tag to all the .content elements and give the margin to the p tag.

p {
  margin:10px;
} 

Giving margin or padding to the p tag won’t affect the animation as it’s inside the main .content container.

There you have it.

You can download the source code below

Download Full Source Code

Thank you for reading and if you’ve any questions, feel free to comment below.

Happy coding!