Dropdown Navigation

Note: These examples are kinda old and this page should be updated. The things that should be updated include: ARIA?, do you need to use list tags or should you just use anchor tags directly in the nav? Also, some of these solutions require JavaScript which is not a requirement of this class - but I'll help you if you want to use it though.

Complex "Dropdown" Menus

If the site has more than a couple pages, then the navigation needs to contain more links and it will be a little more complicated. This navigation contains a list of links like the above example. However the list also contains a sublist. The sublist is hidden by default. The submenu is displayed when the user mouses over the parent link (Programming), or tabs to that link with the keyboard, then presses the Enter key to open the submenu.

Dropdown" (or "flyout") menus are fairly common on larger sites.

However, this kind of menu is NOT ACCESSIBLE unless you add some JavaScript that helps add some ARIA attributes to the HTML.

tab to Programming, then press the "Enter" key. That will open up the menu. When the dropdown menu is open, you can tab to its links

<nav aria-label="Main Navigation">
  <ul>
    <li><a href="resume.html" title="My resume.">Resume</a></li>
    <li class="has-submenu">
      <a href="programming.html" title="My programming experience." aria-haspopup="true" aria-expanded="false">Programming</a>
      <ul>
        <li><a href="basic.html" title="Basic language experience.">BASIC</a></li>
        <li><a href="cobol.html" title="COBOL language experience.">COBOL</a></li>
        <li><a href="java.html" title="Java language experience.">Java</a></li>
        <li><a href="javascript.html" title="JavaScript language experience.">JavaScript</a></li>
        <li><a href="rbasic.html" title="R/Basic language experience.">R/BASIC</a></li>
        <li><a href="rpg.html" title="RPG language experience.">RPG</a></li>
        <li><a href="perl.html" title="Perl language experience.">Perl</a></li>
        <li><a href="php.html" title="PHP language experience.">PHP</a></li>
        <li><a href="python.html" title="Python language experience.">Python</a></li>
      </ul>
    </li>
    <li><a href="education.html" title="My educational journey.">Education</a></li>
    <li><a href="design.html" title="My design portfolio.">Design</a></li>
    <li><a href="hobbies.html" title="My hobbies and off-work activities.">Hobbies</a></li>
    <li><a href="contact.html" title="Contact me.">Contact</a></li>
  </ul>
</nav>

JavaScript Required

This additional JavaScript is required in order to clearly communicate the content of the navigation with users of screenreaders.

This script:

  1. Automatically runs when the page is loaded.
  2. Reads all the li elements that have a class of 'has-submenu'
  3. Loops those those elements and adds event listeners for both the 'mouseover' and 'mouseout' events.
  4. If mouseover, it adds the 'open' class to the submenu.
  5. If mouseout, it removes the 'open' class from the submenu.
  6. Loops over those 'has-submenu' elements again and adds an event listener for the 'click' event.
  7. If click and not open, then it adds the 'open' class to the submenu and sets the 'aria-expanded' attribute to 'true'.
  8. If click and open, then it removes the 'open' class from the submenu and sets the 'aria-expanded' attribute to 'false'.

This script should be placed in an external file.

/**
 * Based upon information and code from: https://www.w3.org/WAI/tutorials/menus/flyout/
 * Converted to a Self-Executing Anonymous Function to limit scope.
 *
 **/
(function(){
	let debug = true ;
	let timer ;

	var menuItems = document.querySelectorAll('li.has-submenu');
	if ( debug ) { console.log("menuItems:", menuItems) ; }

	Array.prototype.forEach.call(menuItems, function(el, i){
		el.addEventListener("mouseover", function(event){
			this.className = "has-submenu open";
			clearTimeout(timer);
		});
		el.addEventListener("mouseout", function(event){
			timer = setTimeout(function(event){
				document.querySelector(".has-submenu.open").className = "has-submenu";
			}, 500);
		});
	});

	Array.prototype.forEach.call(menuItems, function(el, i){
		el.querySelector('a').addEventListener("click",  function(event){
			if (this.parentNode.className == "has-submenu") {
				this.parentNode.className = "has-submenu open";
				this.setAttribute('aria-expanded', "true");
				if ( debug ) { console.log("Setting className 'has-submenu open'.","Setting 'aria-expanded' to true.") ; }
			} else {
				this.parentNode.className = "has-submenu";
				this.setAttribute('aria-expanded', "false");
				if ( debug ) { console.log("Setting className 'has-submenu'.","Setting 'aria-expanded' to false.") ; }
			}
			event.preventDefault();
			return false;
		});
	});
})();

Beginning CSS

The following CSS will hide the submenu by default. You will need to add you own additonal CSS to style the menu. You can inspect this menu using the web dev tools to see the old CSS that was used in this example.

.has-submenu ul { 
    display: none;
}

.has-submenu.open ul { 
    display: block;
}

Mobile Navigation

Navigation on mobile devices is complicated.

Here is one example of mobile navigation that is responsive and accessible: https://www.a11ymatters.com/pattern/mobile-nav/.