No Time Dad

A blog about web development written by a busy dad

Simple Example of How to Build an Accordion with CSS and JavaScript

Intro

I like accordions. I think they are a great way to show and hide certain types of content on a page and they add some level of interactivity that can be fun, in my opinion.

I’ll be going through some steps here to show you how to make a simple version of an accordion that is implemented in CSS and JavaScript. I mostly used Bootstrap’s accordion as the inspiration for the version here.

Getting Started

My best advice when starting any new component or feature is to start by getting a bare-bones example working first, then move on with styling it or making it more fancy. If it doesn’t work right then the styling isn’t really going to help you much. So, that is what we’re going to do here. We’ll start with just a button and a content div.

The main idea with an accordion is that you click on a bottom and your content is shown or hidden. Preferably, the show and hide happens with a css transition. Showing and hiding divs can be done by toggling css selectors that contain display: block; and display: hidden;. Below is an example of what something like that would look like.

...
<style>
  .show {
    display: block;
  }

  .hide {
    display: hide;
  }
</style>
<button>Click me!</button>
<div class="hide">My content</div>
<script>
  ...some JavaScript to toggle the show selector on button click...
</script>
...

But, it will be tricky to implement the classic ease effect if we go that route, so we’ll need a different approach. If you look at the Bootstrap accordion you’ll see that the content sort of grows in and shrinks out. Looking at how Bootstrap have implemented their accordion we can see that instead of changing the display property they change the height property. This is a pretty clever approach that allows a really nice ease transition. When page is first loaded we’ll set our content to be “hidden”, so it will have a height: 0; and overflow: hidden;.

There is something else we need to take into consideration here though. In order to transition from height: 0 in the “hidden” state, we need to set a height for the “show” state because we will be transitioning on the height property. For example, we’d go from height: 0; to height: 250px;. The problem with that is we might not always know the height of the content we are showing. It could be 250px high, 450px high, or 1000px high. If we set height: 250px and our content is 300px high then we lose 50px worth of content, and that would be bad.

To fix this, we need to make a small consession. If we can get a rough idea of how big our content could potentially be and say “Ok, if it is bigger than xxx pixels than it will get cut off and we’ll deal with it another way”. If we can make that concession then we can use max-height instead of height and allow the content container to be a little more fluid in size. This is not a perfect solution by any means, but it will provide reasonably consistent div sizing, and you can set the max-height pretty high if you’d like. Another thing to consider with this is that if your content really does run the risk of getting chopped off from a high max-height then maybe it should not be in an accordion to begin with. Brutal, I know.

So we’ll be transitioning from max-height: 0; (hidden) to max-height: 500px (shown) in our example. We will have two css selectors for this purpose. The first we’ll call content and the second we’ll call show. As mentioned previously, our content element will default to not having the show class applied. We’ll use a bit of JavaScript to add a click event listener to the button that will toggle the show class on our content element.

Below is our not-fancy-but-working starter code and demo. Note the transition: max-height .3s ease; property which is what gives our content that nice easing effect. Another important thing to note with the code below is the use of button_1 and content_1 element ids. This is so if you had multiple accordions on the page you can toggle them independently.

.content_1 {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
}

.show {
  max-height: 500px;
}
<div>
  <button id="button_1">Toggle</button>
  <div id="content_1" class="content">
    Neque porro quisquam est qui dolorem ipsum quia dolor sit...
  </div>
</div>
<script>
  document.getElementById("button_1").addEventListener("click", () => {
    document.getElementById("content_1").classList.toggle("show")
  })
</script>
Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...

Making It fancy

The accordion isn’t much to look at right now, so let’s make it better. We’ll style the button, improve the spacing, and make the content container look nicer. One thing to be careful of here is adding styles directly to the content selector. For example, if you added a border to the content selector then you’d notice that when the content section is in the hidden state you’d still see the border line. To get around this we’ll add an inner content class, creatively called content_inner, which will allow us to add borders that will be completely hidden because they’re a child of the div that contains the content class.

Our final example and code is below. I included the container selector and div, as well as the custom color palette I used for the example.

Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..
Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...
Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...
:root {
  --charcoal: #264653ff;
  --burnt-sienna: #e76f51ff;
  --cultured: #f8f9faff;
}

.btn {
  width: 100%;
  padding: 0.75rem 1.5rem;
  border: 2px solid var(--burnt-sienna);
  background-color: var(--burnt-sienna);
  color: var(--cultured);
  font-weight: 600;
}

.accordion {
  padding: 1px;
}

.container {
  height: auto;
  background-color: var(--cultured);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 2rem;
}

.content {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
}

.content_inner {
  padding: 1rem;
  border-bottom-left-radius: 0.5rem;
  border-bottom-right-radius: 0.5rem;
  border: 8px dashed var(--burnt-sienna);
  border-top: none;
  color: var(--charcoal);
  font-weight: 400;
}

.show {
  max-height: 500px;
}
<div class="container">
  <div class="accordion">
    <button class="btn" id="button_1">Accordion 1</button>
    <div id="content_1" class="content">
      <div class="content_inner">
        Neque porro quisquam est qui dolorem ipsum quia dolor sit amet,
        consectetur, adipisci velit..
      </div>
    </div>
  </div>
  <div class="accordion">
    <button class="btn" id="button_2">Accordion 2</button>
    <div id="content_2" class="content">
      <div class="content_inner">
        Neque porro quisquam est qui dolorem ipsum quia dolor sit amet,
        consectetur, adipisci velit..
      </div>
    </div>
  </div>
  <div class="accordion">
    <button class="btn" id="button_3">Accordion 3</button>
    <div id="content_3" class="content">
      <div class="content_inner">
        Neque porro quisquam est qui dolorem ipsum quia dolor sit amet,
        consectetur, adipisci velit..
      </div>
    </div>
  </div>
</div>
<script>
  document.getElementById("button_1").addEventListener("click", () => {
    document.getElementById("content_1").classList.toggle("show")
  })
  document.getElementById("button_2").addEventListener("click", () => {
    document.getElementById("content_2").classList.toggle("show")
  })
  document.getElementById("button_3").addEventListener("click", () => {
    document.getElementById("content_3").classList.toggle("show")
  })
</script>