Only allowing one open dropdown at a time with the details element
Yesterday, we looked at how to use the <details>
element to create JavaScript-free dropdown menus.
With that setup, each dropdown acts independently of the other. There’s a good chance that when one dropdown opens, you want any open dropdowns to close so that they don’t overlap with each other.
<nav>
<ul class="my-nav">
<li>
<details class="dropdown">
<summary>This has dropdown items</summary>
<ul>
<li><a href="#hi">Hi</a></li>
<li><a href="#universe">Universe</a></li>
</ul>
</details>
</li>
<li>
<details class="dropdown">
<summary>Another dropdown</summary>
<ul>
<li><a href="#goodbye">Goodbye</a></li>
<li><a href="#universe">Universe</a></li>
</ul>
</details>
</li>
</ul>
</nav>
See how these two dropdowns overlap each other?
Today, let’s look at a super tiny bit of JavaScript you can use to close any open dropdowns when opening a new one.
Quick Aside
If you’re going to write JavaScript anyways, why not write your own custom dropdown menu instead?
A few reasons:
- If our JavaScript fails, this native dropdown menu will still work, just with reduced functionality.
- In browsers that don’t support the
<details>
element, users can still access the content. - The native element handles accessibility concerns like keyboard interactions and focus management for you.
- It’s a lot less JavaScript than you’d have to write otherwise. Take the win!
Listing for toggle
events
First, we’ll listen for a special event called toggle
on the .my-nav
navigation. This event fires on a <details>
element when it’s opened or closed.
This event doesn’t bubble, so you’ll need to set useCapture
to true
.
var nav = document.querySelector('.my-nav');
nav.addEventListener('toggle', function (event) {
// Do stuff...
}, true);
If the clicked element isn’t open
, we’ll do nothing.
Otherwise, we’ll get all of the open dropdowns in our nav
element, and close them by removing the [open]
attribute. We’ll check that the item isn’t the one we just opened first, though.
var nav = document.querySelector('.my-nav');
nav.addEventListener('toggle', function (event) {
// Only run if the dropdown is open
if (!event.target.open) return;
// Get all other open dropdowns and close them
var dropdowns = nav.querySelectorAll('.dropdown[open]');
Array.prototype.forEach.call(dropdowns, function (dropdown) {
if (dropdown === event.target) return;
dropdown.removeAttribute('open');
});
}, true);
And that’s it! Here’s the code in action.