Wordpress-style Accessible Dropdown Menu With jQuery

File Size: 3.39 KB
Views Total: 5643
Last Update:
Publish Date:
Official Website: Go to website
License: MIT
   
Wordpress-style Accessible Dropdown Menu With jQuery

A small script to create a wordpress-style, WAI-ARIA compliant, fully responsive, cross-platform drop down menu using jQuery and CSS3 animations.

How to use it:

1. Create the dropdown menu from a nested nav list as follows:

<nav class="site-navigation" role="navigation" aria-label="Main Navigation">
  <ul class="nav">
    <li class="menu-item active"><a href="#">Home</a></li>
    <li class="menu-item-has-children"><a href="#">Categories</a>
      <ul class="sub-menu">
        <li><a href="#">jQuery</a></li>
        <li><a href="#">React</a></li>
        <li><a href="#">Angular</a></li>
        <li><a href="#">Vue.js</a></li>
      </ul>
    </li>
    <li class="menu-item menu-item-has-children"><a href="#fallback-page">Featured</a>
      <ul class="sub-menu">
        <li><a href="#">Plugins</a></li>
        <li><a href="#">Components</a></li>
        <li><a href="#">Directives</a></li>
      </ul>
    </li>
    <li class="menu-item"><a href="#">About</a></li>
    <li class="menu-item"><a href="#">Contact</a></li>
  </ul>
</nav>

2. The primary CSS/CSS3 styles for the dropdown naivgation and sub menus.

.site-navigation {
  background: #fff;
  border-radius: 3px;
  box-shadow: 0 0.25em 2em rgba(128, 103, 253, 0.4);
  color: #8b5ab7;
  font: 700 1.375rem / 1 'Roboto', sans-serif;
  max-width: 996px;
  margin: 2em auto;
  text-align: center;
}

.site-navigation ul, .site-navigation li {
  list-style: none;
  margin: 0;
  padding: 0;
}

.nav > li {
  border-bottom: 2px solid #e4e3ed;
  position: relative;
}

.nav a {
  color: inherit;
  text-decoration: none;
  -webkit-transition: 0.2s;
  transition: 0.2s;
  display: block;
  overflow: hidden;
  padding: 1.25em;
  position: relative;
}

.nav a::before {
  background-color: #fc9bb6;
  background-image: -webkit-linear-gradient(315deg, #fff 0.13333em, transparent 0.13333em), -webkit-linear-gradient(225deg, #fff 0.13333em, transparent 0.13333em), -webkit-linear-gradient(135deg, #fff 0.13333em, transparent 0.13333em), -webkit-linear-gradient(45deg, #fff 0.13333em, transparent 0.13333em);
  background-image: linear-gradient(135deg, #fff 0.13333em, transparent 0.13333em), linear-gradient(225deg, #fff 0.13333em, transparent 0.13333em), linear-gradient(315deg, #fff 0.13333em, transparent 0.13333em), linear-gradient(45deg, #fff 0.13333em, transparent 0.13333em);
  background-position: -0.2em 0, -0.2em 0, 0 0, 0 0;
  background-size: 0.4em 0.4em;
  content: '';
  display: block;
  position: absolute;
  top: 50%;
  right: 1.25em;
  left: 1.25em;
  margin-right: 8px;
  margin-top: 0.75em;
  height: 0.4em;
  -webkit-transform: translateY(3em);
  transform: translateY(3em);
  -webkit-transition: 0.3s ease-out;
  transition: 0.3s ease-out;
}

.nav a:hover, .nav a:focus { color: #ffbb61; }

.nav a:hover::before, .nav a:focus::before {
  -webkit-transform: none;
  transform: none;
}

.nav a:focus { outline: 2px dotted #ffbb61; }

.nav a:active { color: #50cba1; }

.menu-item-has-children > a::after {
  border: 0.25em solid transparent;
  border-top-color: #ffbb61;
  border-bottom-width: 0;
  content: '';
  display: inline-block;
  -webkit-transition: 0.2s;
  transition: 0.2s;
  margin-top: -0.125em;
  margin-left: 0.5em;
  position: relative;
  vertical-align: middle;
}

.menu-item-has-children > a[aria-expanded=true]::after {
  -webkit-transform: rotate(180deg);
  transform: rotate(180deg);
}

li.active a::after {
  border: 0.25em solid transparent;
  border-bottom-color: #50cba1;
  border-top-width: 0;
  content: '';
  position: absolute;
  bottom: 0;
  left: 50%;
  margin-left: -0.125em;
}

.sub-menu {
  opacity: 0;
  -webkit-transform: translate3d(0, -2rem, 0) scale(0.8);
  transform: translate3d(0, -2rem, 0) scale(0.8);
  visibility: hidden;
}

[aria-expanded=true] + .sub-menu {
 opacity: 1;
 -webkit-transform: none;
 transform: none;
 visibility: visible;
}

.sub-menu {
  background: #fff;
  box-shadow: 0 0.25em 2em rgba(128, 103, 253, 0.4);
  position: absolute;
  top: 100%;
  left: 50%;
  -webkit-transform-origin: top center;
  transform-origin: top center;
  -webkit-transition: 0.3s ease-out;
  transition: 0.3s ease-out;
  width: 11rem;
  z-index: 2;
}

.sub-menu.sub-menu { margin-left: -5.5rem; }

.sub-menu li { border: 1px solid #e4e3ed; }

.sub-menu a {
  -webkit-transform: translate3d(0, 1em, 0);
  transform: translate3d(0, 1em, 0);
}

.sub-menu li:nth-child(1) a {
  -webkit-transition-delay: 0.1s;
  transition-delay: 0.1s;
}

.sub-menu li:nth-child(2) a {
  -webkit-transition-delay: 0.2s;
  transition-delay: 0.2s;
}

.sub-menu li:nth-child(3) a {
  -webkit-transition-delay: 0.3s;
  transition-delay: 0.3s;
}

.sub-menu li:nth-child(4) a {
  -webkit-transition-delay: 0.4s;
  transition-delay: 0.4s;
}

.sub-menu li:nth-child(5) a {
  -webkit-transition-delay: 0.5s;
  transition-delay: 0.5s;
}

[aria-expanded=true] + .sub-menu a {
 -webkit-transform: none;
 transform: none;
}

3. Convert the dropdown menu into a vertical toggle menu on mobile devices.

@media (min-width: 768px) {

.nav {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -ms-flex-pack: distribute;
  justify-content: space-around;
}

.nav > li {
  border: 0;
  -webkit-box-flex: 1;
  -ms-flex: 1 1 auto;
  flex: 1 1 auto;
}

.nav > li:not(:last-child)::before {
  background: #e4e3ed;
  content: '';
  display: block;
  -webkit-transform: skew(-15deg);
  transform: skew(-15deg);
  width: 4px;
  position: absolute;
  top: 0;
  bottom: 0;
  right: 2px;
  z-index: 10;
}
}

4. Include the necessary jQuery JavaScript library at the bottom of the webpage.

<script src="//code.jquery.com/jquery-3.2.1.min.js"></script> 

5. The JavaScript to enable the accessible dropdown menu.

(function() {
  var $allMenus = $(".menu-item-has-children");
  var $allToggles = $allMenus.find('> a');
  var $allTopLinks = $(".nav > li > a");
  
  var hoverTimer, blurTimer, 
      delay = 500;

  // Reusable functions
  function openMenu($current) {
    $allToggles.attr("aria-expanded", "false");
    $current.attr("aria-expanded", "true");
  }

  function closeMenu($current) {
    $current.attr("aria-expanded", "false");
  }

  function focusSubmenu($current) {
    $current.on("transitionend", function() {
      if ($current.css("visibility") === "visible") {
        $current.find("li:first-child a").focus();
        $current.off("transitionend");
      } 
    });
  }

  // Add aria roles
  $(".menu-item.active > a").attr("aria-current", "page");
  $allToggles.attr({
    "aria-haspopup": "true",
    "aria-expanded": "false",
    "role": "button"
  });

  // Open menu on hover
  $allMenus.on("mouseenter", function(e) {
    openMenu($(this).find("[aria-expanded]"));

    clearTimeout(hoverTimer);
  });

  // Close menu after a short delay
  $allMenus.on("mouseleave", function() {
    $element = $(this).find("[aria-expanded]");

    hoverTimer = setTimeout(function() {
      closeMenu($element);
    }, delay);
  });

  // Toggle menu on click, tap, or focus + enter/space
  $allToggles
    .on("click touchstart", function(e) {
      $this = $(this);
      $submenu = $this.next(".sub-menu");

      if ($this.attr("aria-expanded") === "true") closeMenu($this);
      else openMenu($this);

      focusSubmenu($submenu);

      e.preventDefault();
    })
    .on("keyup", function(e) {
      if (e.keyCode === 32) {
        openMenu($(this));
        focusSubmenu($(this).next(".sub-menu"));
      }
    });

  // Close menu when refocusing on top-level links
  $allTopLinks.on("focus", function() {
    closeMenu($allToggles);
  });

  // Close menu on esc and focus loss
  $(".site-navigation").on("keyup", function(e) {
    if (e.keyCode === 27) closeMenu($allToggles);
  });
  
  // Close menu if focus isn't inside site navigation
  $('.sub-menu').on('focusout', function(){
    // There's a delay between focusout and re-focus
    setTimeout( function() {
      $focused = $(document.activeElement);
      if($focused.closest('.site-navigation').length === 0 ) {
        closeMenu($allToggles);
      }
    }, 1);
  });

})();

This awesome jQuery plugin is developed by giana. For more Advanced Usages, please check the demo page or visit the official website.