Nested Accordion Menu With Vanilla JS

Category: Accordion , Javascript , Menu & Navigation | April 16, 2021
Author:tomaszbujnowicz
Views Total:3,081 views
Official Page:Go to website
Last Update:April 16, 2021
License:MIT

Preview:

Nested Accordion Menu With Vanilla JS

Description:

An animated multi-level accordion menu built with nested nav list and vanilla JavaScript.

How to use it:

1. The required markup structure for the nested accordion menu.

<div class="accordion-menu-wrapper active" id="menu-parent" data-accordion-wrapper>
  <nav role="navigation" class="accordion-menu">
    <ul class="accordion-menu__list">
      <li class="accordion-menu__item">
        <a
          href="#menu-1"
          class="accordion-menu__link"
          data-accordion-menu>
          <span>
            Menu 1 Dropdown
          </span>
          <span class="accordion-menu__icon"></span>
        </a>
        <ul class="accordion-menu__sublist accordion-menu--hidden" id="menu-1">
          <li>
            <a href="#" class="accordion-menu__sublink">
              Child Menu 1
            </a>
          </li>
          <li>
            <a
              href="#menu-1-1"
              class="accordion-menu__link"
              data-accordion-menu>
              <span>
                Child Menu 2 Dropdown
              </span>
              <span class="accordion-menu__icon"></span>
            </a>
            <ul class="accordion-menu__sublist accordion-menu--hidden" id="menu-1-1">
              <li>
                <a href="#" class="accordion-menu__sublink">
                  Grand Child Menu 1
                </a>
              </li>
              <li>
                <a href="#" class="accordion-menu__sublink">
                  Grand Child Menu 2
                </a>
              </li>
            </ul>
          </li>
        </ul>
      </li>
      <li class="accordion-menu__item">
        <a
          href="#menu-2"
          class="accordion-menu__link"
          data-accordion-menu>
          <span>
            Menu 2 Dropdown
          </span>
          <span class="accordion-menu__icon"></span>
        </a>
        <ul class="accordion-menu__sublist accordion-menu--hidden" id="menu-2">
          <li>
            <a href="#" class="accordion-menu__sublink">
              Child Menu 2
            </a>
          </li>
          <li>
            <a
              href="#menu-2-1"
              class="accordion-menu__link"
              data-accordion-menu>
              <span>
                Child Menu 2 Dropdown
              </span>
              <span class="accordion-menu__icon"></span>
            </a>
            <ul class="accordion-menu__sublist accordion-menu--hidden" id="menu-2-1">
              <li>
                <a href="#" class="accordion-menu__sublink">
                  Grand Child Menu 1
                </a>
              </li>
              <li>
                <a href="#" class="accordion-menu__sublink">
                  Grand Child Menu 2
                </a>
              </li>
            </ul>
          </li>
        </ul>
      </li>
      <li class="accordion-menu__item">
        <a href="#" class="accordion-menu__link">
          Submenu item
        </a>
      </li>        
    </ul>
  </nav>
</div>

2. The basic styling of the nested accordion menu.

.accordion-menu--hidden {
  display: none;
}    
.accordion-menu__list,
.accordion-menu__sublist {
  list-style-type: none;
  margin: 0;
  padding: 0;
}
.accordion-menu__list {
  border-top: 1px #eee solid;
}
.accordion-menu__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 1rem;
  padding-bottom: 1rem;
  border-bottom: 1px #eee solid;
}
.accordion-menu__icon {
  position: relative;
  width: 16px;
  height: 16px;
  transition: .2s all;     
}
.accordion-menu__icon:before,
.accordion-menu__icon:after {
  content: "";
  display: block;
  background-color: #333;
  position: absolute;   
  top: 50%; left: 0;
  transition: .35s;
  width: 100%;
  height: 2px; 
}
.accordion-menu__icon:before {
  transform: translateY(-50%);
}
.accordion-menu__icon:after {
  transform: translateY(-50%) rotate(90deg);
}
.accordion-menu--active .accordion-menu__icon:before {
  transform: translateY(-50%) rotate(-90deg);
  opacity: 0;
}
.accordion-menu--active .accordion-menu__icon:after {
  transform: translateY(-50%) rotate(0);
}
.accordion-menu__sublist {
  margin-left: 1.25rem;
}
.accordion-menu__sublink {
  display: block;
  padding-top: 1rem;
  padding-bottom: 1rem;
  border-bottom: 1px #eee solid;
}
.accordion-menu-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 1rem;
  overflow: scroll;
}
.accordion-menu-nav__link:not(:last-child) {
  margin-right: 1rem;
}
.accordion-menu-wrapper {
  display: none;
}
.accordion-menu-wrapper.active {
  display: block;
}

3. The main function.

var accordionMenu = function () {
    /**
    * Element.closest() polyfill
    * https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
    */
    if (!Element.prototype.closest) {
      if (!Element.prototype.matches) {
        Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
      }
      Element.prototype.closest = function (s) {
        var el = this;
        var ancestor = this;
        if (!document.documentElement.contains(el)) return null;
        do {
          if (ancestor.matches(s)) return ancestor;
          ancestor = ancestor.parentElement;
        } while (ancestor !== null);
        return null;
      };
    }
    // Listen for click on the document
    // Accordiom menu functionality
    document.addEventListener('click', function (event) {
      // Bail if our clicked element doesn't match
      var trigger = event.target.closest('[data-accordion-menu]');
      if (!trigger) return;
      // Get the target content
      var target = document.querySelector(trigger.hash);
      if (!target) return;
      // Prevent default link behavior
      event.preventDefault();
      // Toggle our content
      target.classList.toggle('accordion-menu--hidden');
      // Toggle trigger class
      trigger.classList.toggle('accordion-menu--active');
    });
    // Listen for click on the document
    // Accordion parent menu functionality
    document.addEventListener('click', function (event) {
      // Bail if our clicked element doesn't match
      var trigger = event.target.closest('[data-accordion-menu-nav]');
      if (!trigger) return;
      // Get the target content
      var target = document.querySelector(trigger.hash);
      if (!target) return;
      // Prevent default link behavior
      event.preventDefault();
      // If the content is already expanded, collapse it and quit
      if (target.classList.contains('active')) {
        target.classList.remove('active');
        return;
      }
      // Get all open accordion content, loop through it, and close it
      var accordions = document.querySelectorAll('[data-accordion-wrapper]');
      for (var i = 0; i < accordions.length; i++) {
        accordions[i].classList.remove('active');
      }
      // Toggle our content
      target.classList.toggle('active');
    });
};

4. Activate the nested accordion menu.

accordionMenu();

You Might Be Interested In:


Leave a Reply