Go to homepage

Projects /

Multi-Level Accordion Menu

A simple CSS accordion menu with support for sub level items.

Multi-Level Accordion Menu
Check our new component library →

Today's resource is a handy accordion menu with support for groups/subitems. It works with CSS only, using the :checked pseudo-class selector on the checkboxes input elements. However we included a version with JavaScript as well, in case you prefer a subtle animation compared to the instant default effect. Your call!

The first user case I can think of for this resources is a "layer organizer". Think of Sublime Text sidebar, or Photoshop layer window. Anyway, I'm sure you'll find a use for this new snippet to store in your arsenal ;)

👋 A new version of this component is available. Download now →.

Creating the structure

The HTML structure is pretty simple: the accordion is an unordered list. If a list item contains subitems, then we insert an input[type=checkbox] and its label. Also, we add the .cd-accordion__item--has-children class to the list item. All "standard" list items contain just an anchor tag.

<ul class="cd-accordion margin-top-lg margin-bottom-lg">
  <li class="cd-accordion__item cd-accordion__item--has-children">
    <input class="cd-accordion__input" type="checkbox" name ="group-1" id="group-1">
    <label class="cd-accordion__label cd-accordion__label--icon-folder" for="group-1"><span>Group 1</span></label>

    <ul class="cd-accordion__sub cd-accordion__sub--l1">
      <li class="cd-accordion__item cd-accordion__item--has-children">
        <input class="cd-accordion__input" type="checkbox" name ="sub-group-1" id="sub-group-1">
        <label class="cd-accordion__label cd-accordion__label--icon-folder" for="sub-group-1"><span>Sub Group 1</span></label>

        <ul class="cd-accordion__sub cd-accordion__sub--l2">
          <li class="cd-accordion__item"><a class="cd-accordion__label cd-accordion__label--icon-img" href="#0"><span>Image</span></a></li>
          <li class="cd-accordion__item"><a class="cd-accordion__label cd-accordion__label--icon-img" href="#0"><span>Image</span></a></li>
          <li class="cd-accordion__item"><a class="cd-accordion__label cd-accordion__label--icon-img" href="#0"><span>Image</span></a></li>
        </ul>
      </li>
  
      <li class="cd-accordion__item"><a class="cd-accordion__label cd-accordion__label--icon-img" href="#0"><span>Image</span></a></li>
      <li class="cd-accordion__item"><a class="cd-accordion__label cd-accordion__label--icon-img" href="#0"><span>Image</span></a></li>
    </ul>
  </li>

  <li class="cd-accordion__item cd-accordion__item--has-children">
    <a class="cd-accordion__label cd-accordion__label--icon-img" href="#0"><span>Image</span></a>
  </li>
  <li class="cd-accordion__item cd-accordion__item--has-children">
    <a class="cd-accordion__label cd-accordion__label--icon-img" href="#0"><span>Image</span></a>
  </li>
</ul> <!-- cd-accordion -->

Adding style

We use a smart (and quite standard nowadays) technique to detect the click and show sub content with CSS only: by including a checkbox input element, we can use the :checked pseudo-class and the general sibling selector (div ~ div) to change the display mode of the sub <ul> element from "none" to "block".

Step by step: first of all, we have to make sure that the checkbox input element covers the entire list item that contains subitems. Put in other words: we need to create a custom checkbox.

So, firstly, you need to make sure that when you click on the label, the checkbox is checked/unchecked as well. This is achieved by using the "for" attribute inside the label (label "for" attribute = input "name" and "id" attributes. See html section above). This way you can simply hide the input element and work with the label instead.

.cd-accordion__input { // hide native checkbox
  position: absolute;
  opacity: 0;
}

.cd-accordion__label {
  position: relative;
  display: flex;
  align-items: center;
  padding: var(--space-sm) var(--space-md);
  background: var(--cd-color-1);
  box-shadow: inset 0 -1px lightness(var(--cd-color-1), 1.2);
  color: var(--color-white);
}

Now notice in the HTML structure that input, label and the unordered list (that we make visible on click) are siblings. By using the :checked pseudo-class, you can set the following process in motion: when the checkbox input is checked (click on label), take the .cd-accordion__sub sibling element and change its display value from "none" to "block":

.cd-accordion__sub {
  display: none; // by default hide all sub menus
}

.cd-accordion__input:checked ~ .cd-accordion__sub { // show children when item is checked
  display: block;
}

If you want to gently animate the opening phase, make sure to add the .cd-accordion--animated class to the main .cd-accordion element.

Level up your CSS skills

Each month we email a 1-minute CSS tutorial to 20K developers

Awesome! We just sent you a confirmation link by email

Error - please try again or contact us

Your email address is already subscribed

Project duplicated

Project created

Globals imported

There was an error while trying to export your project. Please try again or contact us.