How To Create a Dropdown Navigation Menu with Details-Summary Tutorial

The HTML details element comes with a surprise – in most browsers it has the ability to hide and show content with no additional JavaScript or CSS whatsoever. Here’s a little bit about how it works.

details has with a child called summary, and when a page first loads, the summary is the only part of the element that’s visible, along with a triangle that browsers display by default, to suggest the expandable nature of the content.

Interacting with the summary element, by clicking or using the keyboard, will make the rest of the details element visible and add an open attribute to the details element itself.

This functions as what MDN calls a “disclosure widget“, and in this post, we’ll look at how to use this as the basis for an accessible dropdown navigation element that can be opened equally well by keyboard users tabbing through the page, and mouse users hovering on the nav item. This is a technique we’ve used several times at Content Thread, but it’s also used prominently elsewhere on the web, including on GitHub’s website, as outlined in this helpful slideshow by Mu-An Chiou.

Getting Started

All we need is two HTML elements and some content:

<details>
	<summary>
		Best Foods
	</summary>
        Some information about the best foods!
</details>

If you create these elements, you’ll notice the browser immediately creates an interactive widget, and if you inspect the DOM you can see the open attribute being added and removed as you click the summary.

The first thing we’ll probably want to do is remove that triangle with CSS:

summary::-webkit-details-marker {
	display: none;
}

Adding Menu Content

It’s worth noting here that the content of these elements can be HTML you like, it’s not limited to text. So our summary element can contain the navigation heading, and the rest of the item can be an unordered list, for example:

<details>
	<summary>
		Best Foods
	</summary>
	<div class="dropdown-wrapper">
	<ul>
		<li><a href="#">Shephard's Pie</a></li>
		<li><a href="#">Empanadas</a></li>
		<li><a href="#">Pound Cakes</a></li>
		<li><a href="#">Tacos</a></li>
		<li><a href="#">ALL OTHER PIES</a>
        </ul>
	</div>
</details>

At this point, we could add some CSS to style your menu and call it a day. The menu will open and close when clicked, be resilient to what browser it might be opened in, and be accessible to assistive technology of all kinds.

There are, however, two requests often made by clients that haven’t been served yet: animating the menu open/close action and out, and activating it by mouse hover.

These can both be achieved with JavaScript, and I’d consider them progressive enhancements as they would not be expected to work if JavaScript is disabled anyway.

Animating the menu

We can get halfway there with CSS alone. It’s straightforward to animate your menu in when it first opens by targeting with the CSS [open] attribute selector and a CSS animation:

details[open] .dropdown-wrapper {
	animation: fade-in 0.5s forwards;
}

@keyframes fade-in {
	0% {
		transform: translateY(-20px);
		opacity: 0;
	}
	100% {
		transform: translateY(0);
		opacity: 1;
	}
}

Note that dropdown-wrapper class above matches the dropdown-wrapper class around our ul element above, and we are nesting all dropdown content inside this class.

There’s an important wrinkle when it comes to animating the menu away. When the user closes the menu, it will always disappear instantly, because the open attribute is, by default, removed immediately when the user clicks that summary element. In order to gracefully animate your menu out when it closes, we need some JavaScript.

Here we can listen for clicks on the details element, and call preventDefault() on the click event, then use setTimeout() to determine exactly when that open attribute should be removed. This gives us time to trigger the closing animation with CSS. This click event listener will also fire when a keyboard user hits space or enter while the element is focused, which means no further listeners are needed for keyboard actions!

let summary = document.querySelector("summary");

summary.addEventListener("click", function(event) {
	// first a guard clause: don't do anything 
	// if we're already in the middle of closing the menu.
	if (details.classList.contains("summary-closing")) {
		return;
	}
	// but, if the menu is open ...
	if (details.open) {
		// prevent default to avoid immediate removal of "open" attribute
		event.preventDefault();
		// add a CSS class that contains the animating-out code
		details.classList.add("summary-closing");
		// when enough time has passed (in this case, 500 milliseconds),
		// remove both the "open" attribute, and the "summary-closing" CSS 
		setTimeout(function() {
			details.removeAttribute("open");
			details.classList.remove("summary-closing");
		}, 500);
	}
});

Opening on hover

Sub-menus triggered by a mouse pointer hovering on a navigation item are a common pattern. If planned badly these can result in navigation that is *only* accessible to sighted mouse users, but as an enhancement to our details-based dropdown, we can provide this experience as an option without degrading anything for other users. One way to achieve this is by using mouseenter and mouseleave events on the details element to manage the open attribute state.

let details = document.querySelector("details");
let summary = document.querySelector("summary");

// when user hovers over the summary element, 
// add the open attribute to the details element
summary.addEventListener("mouseenter", event => {
	details.setAttribute("open", "open");
});

// when the user moves the mouse away from the details element,
// perform the out-animation and delayed attribute-removal
// just like in the click handler
details.addEventListener("mouseleave", event => {
	details.classList.add("summary-closing");
	setTimeout(function() {
		details.removeAttribute("open");
		details.classList.remove("summary-closing");
	}, 500);
	details.setAttribute("open", "open");
});

BONUS ROUND: Browser Compatibility

This is worth thinking about. details and summary are not supported in Internet Explorer at all, and at the time of writing, they are not supported in Edge – but that will change later this year when Edge becomes a Chromium-based browser. In situations where we need to support IE, the behavior of the elements can be managed with CSS.

Even if we don’t do that, the default experience will be that all content is visible in a browser that doesn’t support details and summary. So IE users won’t miss out on the menu content itself, just on the opening/closing action. Here is some example CSS to manage opening and closing, that relies on the javascript we’ve already written to manage adding and removing the open attribute:

details:not([open]) > .dropdown-wrapper {
  display: none;
}

details[open] > .dropdown-wrapper {
  display: block;
}

The Final Product

This is a minimal CodePen bringing together many of the items discussed above:

See the Pen Details-Summary by Mark Noonan (@marktnoonan) on CodePen.0

 

The nice thing about details/summary is that they really do make a general-purpose disclosure widget. This can be a building block for lots of other hide/show UI patterns, like “read more” links on articles, “more info” buttons, or tooltips. It’s become my go-to for when a client asks for something to be expandable or to add a menu where there wasn’t one before, so hopefully, this is useful to you as well.

SHARE ON
How To Create a Dropdown Navigation Menu with Details-Summary Tutorial

You May Also Like

Leave a Reply

Your email address will not be published.