Fly-out Menus

Use fly-out (or drop-down) menus to provide an overview of a web site’s page hierarchy. It removes the need for multiple page loads provided that users know where to find the information. Application menus are usually implemented this way, too.

People with reduced dexterity, such as tremors, often have trouble operating fly-out menus. For some, it might be impossible. Make sure to provide other ways to the submenu items, for example by repeating them on the page of the parent menu item.

Indicate submenus

Indicate menu items with submenus visually and using markup. Indicate menu items with submenus visually and using markup. In the following example, the submenu is indicated visually by an icon and this WAI-ARIA markup:

  • aria-haspopup="true" declares that a menu item has a submenu.
  • aria-expanded="false" declares that the submenu is hidden.
Code snippet: HTML
<nav aria-label="Main Navigation">
		<ul>
				<li><a href="…">Home</a></li>
				<li><a href="…">Shop</a></li>
				<li class="has-submenu">
						<a href="…" aria-haspopup="true" aria-expanded="false">
							Space Bears
						</a>
						<ul>
								<li><a href="…">Space Bear 6</a></li>
								<li><a href="…">Space Bear 6 Plus</a></li>
						</ul>
				</li>
				<li><a href="…">Mars Cars</a></li>
				<li><a href="…">Contact</a></li>
		</ul>
</nav>

Fly-out functionality

The fly-out functionality is created using CSS and scripting with slightly separate considerations for mouse and keyboard users.

Mouse users

The following example uses this CSS code to show and hide the submenus when the parent menu items are hovered:

Code snippet: CSS
nav > ul li       ul { display: none; }
nav > ul li:hover ul { display: block;}

In addition, scripting is used to slightly delay the immediate closing of submenu items when the mouse leaves the area. This delay makes it easier to use the menu when navigation by a mouse is not very precise.

In the following example, a delay of one second is added using a timer:

Example:
Code snippet: JavaScript
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";
		}, 1000);
	});
});

Keyboard Users

Submenus should not open when using the tab key to navigate through the menu, as keyboard users would then have to step through all submenu items to get to the next top-level item. Instead, consider one of the following approaches.

Use parent as toggle

Use this approach in situations where the parent menu item only summarizes the submenu and doesn’t have to carry out a function, such as linking to a web page. In this case, the submenu is opened by a script when the user activates the top-level item and closed when the focus leaves the submenu.

Note: The value of the href attribute is ignored but you might still want to link to an existing document in case JavaScript is not loaded.

Example:

The following code iterates through all top-level items with the class has-submenu and adds a click event, which opens or closes the submenu depending on its state. Also, the aria-expanded attribute is set to true while the submenu is open, and to false otherwise.

Note: Despite the name, click events are activated regardless of the input method as soon as the link gets activated.

Code snippet: JavaScript
var menuItems = document.querySelectorAll('li.has-submenu');
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");
		} else {
			this.parentNode.className = "has-submenu";
			this.setAttribute('aria-expanded', "false");
		}
		event.preventDefault();
		return false;
	});
});

Use button as toggle

For situations when the parent menu item needs to carry out a function, such as linking to a web page, a separate button can be added to the parent item, to open and close the submenu. This button can also act as a visual indicator for the presence of a submenu.

Example:

The following code adds a button to every top-level menu item with a submenu. When the button is activated, it shows or hides the submenu. The invisible label of the button is set to “show submenu” or “hide submenu”, reflecting the state of the submenu.

Note: If possible, include the name of the parent menu item in the button label; for example: “show Space Bears submenu”.

Code snippet: JavaScript
var menuItems = document.querySelectorAll('li.has-submenu');
Array.prototype.forEach.call(menuItems, function(el, i){
	var btn = '<button><span><span class="visuallyhidden">show submenu</span></span></button>';
	var topLevelLink = el.querySelector('a');
	topLevelLink.innerHTML = topLevelLink.innerHTML + ' ' + btn;

	el.querySelector('a button').addEventListener("click",  function(event){
		if (this.parentNode.parentNode.className == "has-submenu") {
			this.parentNode.parentNode.className = "has-submenu open";
			this.parentNode.setAttribute('aria-expanded', "true");
			this.querySelector('.visuallyhidden').innerText = 'hide submenu';
		} else {
			this.parentNode.parentNode.className = "has-submenu";
			this.parentNode.setAttribute('aria-expanded', "false");
			this.querySelector('.visuallyhidden').innerText = 'show submenu';
		}
		event.preventDefault();
	});
});

These tutorials provide best-practice guidance on implementing accessibility in different situations. This page combined the following WCAG 2.0 success criteria and techniques from different conformance levels:

Techniques: