Responsive Details-Summary Menu
A mobile-responsive multi-level click-down menu using Details-Summary...
Expand and contract this window around the 640px width mark...
The CSS...
<style>
/* The mobile nav styles */
/* Enforce list semantics */
nav [role="list"] {
list-style: none;
padding-left: 1rem;
}
nav details {
display: inline-block;
}
@media (min-width: 40em) { /* 640px */
/* Note: dropdowns may use position absolute,
but was considered beyond scope in this simple demo. */
nav > details > [role="list"] {
display: flex;
gap: 1rem;
margin-top: 0 !important;
padding: 0;
}
nav > details > ul > li {
margin-top: 0 !important;
}
}
</style>
The HTML...
<nav>
<details>
<summary>Mobile menu</summary>
<ul role=list>
<li><a href="#">About us</a></li>
<li>
<details>
<summary>Products</summary>
<ul role=list>
<li><a href="#">Product 1</a></li>
<li><a href="#">Product 2</a></li>
<li>
<details>
<summary>Sub</summary>
<ul role=list>
<li><a href="#">Sub 1</a></li>
<li><a href="#">Sub 2</a></li>
</ul>
</details>
</li>
</ul>
</details>
</li>
<li><a href="#">Insights</a></li>
<li><a href="#">Contact</a></li>
</ul>
</details>
</nav>
The Javascript...
<script>
console.clear();
// The mobile disclosure button only:
// Progressive enhancement, works without JS too.
const responsiveMenuButton = (document => {
'use strict';
const minViewportWidth = '40em'; // @ 640px
const details = document.querySelector('nav > details');
if (!details) return;
const summary = details.querySelector('summary');
if (!summary) return;
const mediaQuery = window.matchMedia(`(min-width: ${minViewportWidth})`);
let isSmallScreen = !mediaQuery.matches;
const showMenu = _ => {
// Edge case: Orientation may change viewport width without moving focus
const isSummaryFocused = !!details.querySelector('summary:is(:focus)');
details.open = true;
summary.hidden = true;
if (isSummaryFocused) {
const firstLink = details.querySelector('a[href]');
firstLink && firstLink.focus();
}
};
const hideMenu = _ => {
// Edge case: Orientation may change viewport width without moving focus
const isMenuLinkFocused = !!details.querySelector('ul:is(:focus-within)');
details.removeAttribute('open');
summary.removeAttribute('hidden');
isMenuLinkFocused && summary.focus();
};
const controlResponsiveMenu = event => {
if (isSmallScreen && event.matches) {
showMenu();
isSmallScreen = true;
}
isSmallScreen && !event.matches && hideMenu();
!isSmallScreen && event.matches && showMenu();
if (!isSmallScreen && !event.matches) {
hideMenu();
isSmallScreen = false;
}
};
mediaQuery.addListener(controlResponsiveMenu);
controlResponsiveMenu(mediaQuery);
})(document);
</script>