DEV Community

Cover image for How to build an Accordion Menu using HTML, CSS and JavaScript
Kingsley Ubah
Kingsley Ubah

Posted on

How to build an Accordion Menu using HTML, CSS and JavaScript

HTML, CSS and JavaScript can help you create stylish and dynamic web elements. One of those kind of element is an Accordion Menu.

In this tutorial, we will build a simple Accordion Menu.

ezgif.com-gif-maker.gif

What is an Accordion?

In UI design, an accordion is a vertically stacked list of information. For each list, there is a labelled header pointing to a corresponding content. The content is hidden by default. Clicking on a particular label will expand its content.

One very common use case for accordions to hold a list of frequently asked questions. Clicking on any question will toggle a corresponding answer.

You can get the code for this project from Codepen

How to build an accordion using HTML, CSS and JS

We begin by defining the markup. If you are using an IDE like VSCode and you do have emmet installed, create a new index.html file and type ! followed by enter. This should create a HTML boilerplate code for your project.

Alternatively, you can copy the following code into your index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>

  <script src="app.js" type="text/javascript"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

The folder structure is simple. We will create a folder called accordion. Inside the folder we will create three files: index.html, styles.css and app.js. We will also link all files into our HTML marjup as observed above.

HTML Markup For the Accordion

The HTML for the accordion is also going to be quite simple.

<body>


  <div class="accordion-body">
  <div class="accordion">
    <h1>Frequently Asked Questions</h1>
    <hr>
    <div class="container">
      <div class="label">What is HTML</div>
      <div class="content">Hypertext Markup Language (HTML) is a computer language that makes up most web pages and online applications. A hypertext is a text that is used to reference other pieces of text, while a markup language is a series of markings that tells web servers the style and structure of a document. HTML is very simple to learn and use.</div>
    </div>
    <hr>
    <div class="container">
      <div class="label">What is CSS?</div>
      <div class="content">CSS stands for Cascading Style Sheets. It is the language for describing the presentation of Web pages, including colours, layout, and fonts, thus making our web pages presentable to the users. CSS is designed to make style sheets for the web. It is independent of HTML and can be used with any XML-based markup language. CSS is popularly called the design language of the web.
</div>
    </div>
    <hr>
    <div class="container">
      <div class="label">What is JavaScript?</div>
      <div class="content">JavaScript is a scripting or programming language that allows you to implement complex features on web pages — every time a web page does more than just sit there and display static information for you to look at — displaying timely content updates, interactive maps, animated 2D/3D graphics, scrolling video jukeboxes, etc. — you can bet that JavaScript is probably involved. It is the third of the web trio.</div>
    </div>
    <hr>
    <div class="container">
      <div class="label">What is React?</div>
      <div class="content">React is a JavaScript library created for building fast and interactive user interfaces for web and mobile applications. It is an open-source, component-based, front-end library responsible only for the application’s view layer. In Model View Controller (MVC) architecture, the view layer is responsible for how the app looks and feels. React was created by Jordan Walke, a software engineer at Facebook. </div>
    </div>
    <hr>
    <div class="container">
      <div class="label">What is PHP?</div>
      <div class="content">PHP is a server-side and general-purpose scripting language that is especially suited for web development. PHP originally stood for Personal Home Page. However, now, it stands for Hypertext Preprocessor. It’s a recursive acronym because the first word itself is also an acronym.</div>
    </div>
    <hr>
    <div class="container">
      <div class="label">What is Node JS?</div>
      <div class="content">Node.js is an open-source, cross-platform, back-end JavaScript runtime environment that runs on the V8 engine and executes JavaScript code outside a web browser. Node.js lets developers use JavaScript to write command line tools and for server-side scripting—running scripts server-side to produce dynamic web page content before the page is sent to the user's web browser. Consequently, Node.js represents a "JavaScript everywhere" paradigm</div>
    </div>
    <hr>
  </div>
  </div>

  <script src="index.js" type="text/javascript"></script>
</body>
Enter fullscreen mode Exit fullscreen mode

At this point, our page will look all bare.

htmlook.png

Styling Up the Accordion using CSS

The accordion has to look good of course. Time to bring some CSS into play.

@import url('https://fonts.googleapis.com/css2?family=Rubik:wght@300&display=swap');

/* Sets the background color of the body to blue. Sets font to Rubik */

body {
  background-color: #0A2344;
  font-family: 'rubik', sans-serif;
}

/* Aligns the heading text to the center. */

h1 {
  text-align: center;
}

/* Sets the width for the accordion. Sets the margin to 90px on the top and bottom and auto to the left and right */

.accordion {
  width: 800px;
  margin: 90px auto;
  color: black;
  background-color: white;
  padding: 45px 45px;
}
Enter fullscreen mode Exit fullscreen mode

With all of these styled applied, here is how our accordion will look like

withcss1.png

Now we need to start doing some work on the inside. First, we position each of the containers (holding both the label and content) to relative. That means we can now position it's children relative to the parent.

.accordion .container {
  position: relative;
  margin: 10px 10px;
}

/* Position the labels relative to the .container. Add padding to the top and bottom and increase font size. Also make it's cursor a pointer */

.accordion .label {
  position: relative;
  padding: 10px 0;
  font-size: 30px;
  color: black;
  cursor: pointer;
}
Enter fullscreen mode Exit fullscreen mode

Notice the difference now

withcss2.png

The next action will be to append a little '+' sign at the end of each list. We will achieve this using the ::before selector. The ::before and ::after selector is used to place content before of after a specified element.

Here, we are inserting '+' before the label. However, we will use the offset properties 'top' and right to place it at the far right corner.


/* Position the plus sign 5px from the right. Center it using the transform property. */\

.accordion .label::before {
  content: '+';
  color: black;
  position: absolute;
  top: 50%;
  right: -5px;
  font-size: 30px;
  transform: translateY(-50%);
}

/* Hide the content (height: 0), decrease font size, justify text and add transition */

.accordion .content {
  position: relative;
  background: white;
  height: 0;
  font-size: 20px;
  text-align: justify;
  width: 780px;
  overflow: hidden;
  transition: 0.5s;
}

/* Add a horizontal line between the contents */

.accordion hr {
  width: 100;
  margin-left: 0;
  border: 1px solid grey;
}
Enter fullscreen mode Exit fullscreen mode

Now our app will look far far better than it previously did

nowbig.png

Bringing In JavaScript

At this point, our accordion is pretty much static. To make it display the content when clicked, we will need to bring in some JavaScript.

Navigate to your app.js file and type in the following

const accordion = document.getElementsByClassName('container');

for (i=0; i<accordion.length; i++) {
  accordion[i].addEventListener('click', function () {
    this.classList.toggle('active')
  })
}
Enter fullscreen mode Exit fullscreen mode

This script will access all of our lists by classname of 'container'.

Then we will loop through the list. For each container, we simply want to add an event listener to it. When it gets clicked, we want to toggle the class "active" on that element.

Now we are going to test this effect. Click the first container with the label What is HTML, open your DevTool (F12 For Chrome on Windows) and inspect it inside of the elements tab.

You should find the active class registered on it

active.png

Clicking on the element again will remove the active class from it.

Completing the App

There is one last thing we need to do. We need to create an active class within an stylesheet. We will define how we want our accordion to look once JavaScript toggles the class on a container.


/* Unhides the content part when active. Sets the height */

.accordion .container.active .content {
  height: 150px;
}

/* Changes from plus sign to negative sign once active */

.accordion .container.active .label::before {
  content: '-';
  font-size: 30px;
}
Enter fullscreen mode Exit fullscreen mode

This is our app in the end.

ezgif.com-gif-maker.gif

Wrapping Up

Thanks for following along. I hope you learnt something useful from this tutorial.

If you are interested in content like this, I make them daily on my blog.

Have a great week.

P/S: If you are learning JavaScript, I created an eBook which teaches 50 topics in JavaScript with hand-drawn digital notes. Check it out here.

Discussion (25)

Collapse
auroratide profile image
Timothy Foster

Very well-made tutorial! Love the imagery and how you annotate/explain the code blocks.

I definitely recommend looking into the details HTML element. Besides no longer needing Javascript, it also makes the accordion more accessible, allowing assistive tools like screen readers to announce when something can be collapsed or expanded.

Using just divs, you would have to manually add aria roles, labels, and possibly controls to achieve the same thing.

Collapse
merri profile image
Vesa Piittinen

Details + summary cannot be used as accordion in an accessible fashion, because <summary /> element has role="button" which means all semantics are lost, and accordions require proper headings for expected navigation behavior. Details works for a single open/collapse element, but not for a full accordion. It is a bit of a shame though, it is almost there to work as a basis for accordion.

See accordion example and requirements for all the things you need to meet for a good accordion implementation.

Collapse
auroratide profile image
Timothy Foster

Indeed you are right! I hadn't realized that technicality between summary and heading, and thought putting a heading in the summary would be enough.

There are other big things missing from a pure details+summary implementation, like collapsing other details and navigation with arrow keys. I suppose it depends on the degree to which a true accordion is needed or if a set of collapsibles is enough.

Collapse
supportic profile image
Supportic • Edited on

Since the button sits inside the H3 you can place role="heading" aria-level="3" on summary and inside just a button to trigger which takes the full width.
Even if it's saying to use a H3 it doesn't make sense to me because this raises the question what would you do when you want to use H2 inside the accordion? You are not allowed to skip headlines as far as I know ^^
Also in the example since they are not using icons displaying with content attribute in CSS, why isn't there aria-hidden true on that icon?

Thread Thread
merri profile image
Vesa Piittinen

The h3 is somewhat irrelevant, you can use other heading levels depending on the hierarchy. h3 however is the most likely level for accordions.

It doesn't make sense to put role="heading" aria-level="3" on summary because then you lose all the reasons for using the details + summary combo, which would be to get the functionality without JavaScript. As you change the semantics you'd also need to add aria-expanded, and at that point it would be the same if you were doing the thing using just div elements. Also I think you'd still lose the semantics inside summary element, so the button inside would not be announced as a button.

In short the point of using native HTML elements is to embrace what the existing features give you for free, such as <button /> providing all the keyboard goodness, tabbability, and submitting forms. And that last one you can opt-out with type="button". Aria is a hard hitting feature that should be the last resort, because when you use it you have to take responsibility of much more to get things right = learn to test the experience using a screenreader.

The "icon" has no content. It is an empty span so there is nothing a screenreader would take by mistake. So there is no need for aria-hidden="true". You only need aria-hidden="true" if there is a chance for irrelevant things to be announced (and thus worse user experience).

Collapse
ubahthebuilder profile image
Kingsley Ubah Author

Hi Timothy,

Thanks a lot.

Collapse
andrewpierno profile image
Andrew Pierno

Hey Kingsley, this is a solid tutorial!
Would you be interested in writing some tutorials for our companies? Happy to pay! You can either DM me on twitter at twitter.com/AndrewPierno or fill out this little airtable form airtable.com/shrN6S3NMZ7oxRXTt.

Collapse
ubahthebuilder profile image
Kingsley Ubah Author

Sent you a DM on Twitter.

Collapse
bakoun profile image
BaKouN

Or you could use the summary html tag who does all of that work for you ! No JS that way !

Collapse
ubahthebuilder profile image
Kingsley Ubah Author

Makes sense.

Collapse
supportic profile image
Supportic

What if the content exceeds the 150px height?

Collapse
ubahthebuilder profile image
Kingsley Ubah Author

Height could be increased.

Collapse
harzjunior profile image
harzjunior

you can use overflow y auto, this will allow you to scroll down once your text exceeds the height limit

Collapse
ubahthebuilder profile image
Kingsley Ubah Author

Perfect!

Collapse
obaino82 profile image
Obaino82

👍

Collapse
tohka200213 profile image
tohka20-0213

In JS part, why did you get elements as HTMLCollection by using getElementsByClassName method?

In this case, I get a NodeList instead and use a forEach method. Because the code becomes simpler.

Collapse
rishnegi7711 profile image
rishnegi7711

Hi!

I am new to front end developement, can someone please tell why we didn't use ::after because I changed it to ::after in my code and couldn't see any difference in the layout

Collapse
akulsr0 profile image
Akul Srivastava

Nice!!! I also made one, with slightly different approach.
vanillaweb.vercel.app/accordion

Collapse
ubahthebuilder profile image
Kingsley Ubah Author

That looks very nice!

Collapse
godwinkachi profile image
Godwin 'Kachi

Great tutorial

Collapse
ubahthebuilder profile image
Kingsley Ubah Author

Thanks.

Collapse
wahyu9kdl profile image
Ahmad Wahyudi

Aw Project simple code HTML.

Audio

Product Simple Www.alhikmah.my.id/p/mp3-al-quran.html

Collapse
koas profile image
Koas

A great step by step guide, great work!

Collapse
ubahthebuilder profile image
Kingsley Ubah Author

Thank you!

Collapse
eclisauce profile image
Markus Andersson

A alternative way is to skip the javascript and go with the label & checkbox solution, since it can get the same behavior without javascript