DEV Community

Raja Tamil
Raja Tamil

Posted on • Originally published at softauthor.com

Create A Simple Accordion Menu Using Vanilla 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?

alt text

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.

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>
Enter fullscreen mode Exit fullscreen mode

alt text

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;
}
Enter fullscreen mode Exit fullscreen mode

alt text

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;
} 
Enter fullscreen mode Exit fullscreen mode

alt text

Finally, hide the .content by default and I will show you how to toggle its visibility using JavaScript in just a moment.

.content {
  display:none;
  padding:10px;
} 
Enter fullscreen mode Exit fullscreen mode

alt text

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");
Enter fullscreen mode Exit fullscreen mode

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", () => {
     });
}
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

alt text

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");
Enter fullscreen mode Exit fullscreen mode

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" ? "-" : "+";
        });
    }
Enter fullscreen mode Exit fullscreen mode

alt text

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 = "+";
                }
            }
        });
    }
Enter fullscreen mode Exit fullscreen mode

alt text

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;
}
Enter fullscreen mode Exit fullscreen mode

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

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

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");
                }
            }

        });
    }
Enter fullscreen mode Exit fullscreen mode

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");
                }
            }

        });
    }
Enter fullscreen mode Exit fullscreen mode

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>
...
Enter fullscreen mode Exit fullscreen mode

alt text

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;
} 
Enter fullscreen mode Exit fullscreen mode

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

alt text

There you have it.

You can download the source code here.

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

Happy coding!

Top comments (0)