Multilevel Navigation
A responsive multi-level navigation bar with CSS-driven dropdown panels on desktop (hover + focus) and a burger-menu accordion on mobile. All animations use CSS transitions. Fully keyboard-accessible with ARIA attributes set by JavaScript.
Code
<nav data-menu-status="closed" class="nav">
<div class="nav-container">
<div class="nav-bg"></div>
<div class="nav-inner">
<a href="#" class="nav-logo">
<!-- your logo SVG -->
</a>
<div class="nav-center">
<ul class="nav-center__list">
<li>
<button data-dropdown-toggle="" class="nav-link">
<span class="nav-link__label">Products</span>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 17 10" fill="none" class="nav-link__dropdown-icon">
<path d="M1.5 1.5L8.5 8.5L15.5 1.5" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
</button>
<div class="nav-dropdown">
<div class="nav-dropdown__overflow">
<div class="nav-dropdown__overflow-inner">
<div class="nav-container">
<ul class="nav-dropdown__content">
<li class="nav-dropdown__content-li">
<a href="#" class="nav-dropdown__link">
<div class="nav-dropdown__link-bg">
<img src="your-image-1.jpg" class="nav-dropdown__img">
<div class="nav-dropdown__img-overlay"></div>
</div>
<div class="nav-dropdown__link-inner">
<span class="nav-dropdown__link-label">Product One</span>
<div class="nav-dropdown__link-bubble">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 16 16" fill="none" class="icon">
<path d="M9.33398 12.6666L14.0007 7.99992L9.33398 3.33325" stroke="currentColor" stroke-miterlimit="10"></path>
<path d="M14.0007 8H1.33398" stroke="currentColor" stroke-miterlimit="10"></path>
</svg>
</div>
</div>
</a>
</li>
<li class="nav-dropdown__content-li">
<a href="#" class="nav-dropdown__link">
<div class="nav-dropdown__link-bg">
<img src="your-image-2.jpg" class="nav-dropdown__img">
<div class="nav-dropdown__img-overlay"></div>
</div>
<div class="nav-dropdown__link-inner">
<span class="nav-dropdown__link-label">Product Two</span>
<div class="nav-dropdown__link-bubble">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 16 16" fill="none" class="icon">
<path d="M9.33398 12.6666L14.0007 7.99992L9.33398 3.33325" stroke="currentColor" stroke-miterlimit="10"></path>
<path d="M14.0007 8H1.33398" stroke="currentColor" stroke-miterlimit="10"></path>
</svg>
</div>
</div>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</li>
<li>
<button data-dropdown-toggle="" class="nav-link">
<span class="nav-link__label">Services</span>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 17 10" fill="none" class="nav-link__dropdown-icon">
<path d="M1.5 1.5L8.5 8.5L15.5 1.5" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
</button>
<div class="nav-dropdown">
<div class="nav-dropdown__overflow">
<div class="nav-dropdown__overflow-inner">
<div class="nav-container">
<ul class="nav-dropdown__content">
<li class="nav-dropdown__content-li">
<a href="#" class="nav-dropdown__link is--static">
<div class="nav-dropdown__link-inner">
<span class="nav-dropdown__link-label">Service One</span>
<div class="nav-dropdown__link-bubble">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 16 16" fill="none" class="icon">
<path d="M9.33398 12.6666L14.0007 7.99992L9.33398 3.33325" stroke="currentColor" stroke-miterlimit="10"></path>
<path d="M14.0007 8H1.33398" stroke="currentColor" stroke-miterlimit="10"></path>
</svg>
</div>
</div>
</a>
</li>
<li class="nav-dropdown__content-li">
<a href="#" class="nav-dropdown__link is--static">
<div class="nav-dropdown__link-inner">
<span class="nav-dropdown__link-label">Service Two</span>
<div class="nav-dropdown__link-bubble">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 16 16" fill="none" class="icon">
<path d="M9.33398 12.6666L14.0007 7.99992L9.33398 3.33325" stroke="currentColor" stroke-miterlimit="10"></path>
<path d="M14.0007 8H1.33398" stroke="currentColor" stroke-miterlimit="10"></path>
</svg>
</div>
</div>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</li>
<li>
<a href="#" class="nav-link"><span class="nav-link__label">About</span></a>
</li>
<li>
<a href="#" class="nav-link"><span class="nav-link__label">News</span></a>
</li>
</ul>
</div>
<div class="nav-end">
<a href="#" class="nav-button md--hide">
<span>Contact Us</span>
</a>
<a href="#" class="nav-button is--primary">
<span>Enquire Now</span>
</a>
<button data-menu-button="" aria-label="toggle menu" class="menu-button">
<div class="menu-button__line"></div>
<div class="menu-button__line"></div>
</button>
</div>
</div>
</div>
<div class="page-bg"></div>
</nav>:root{
--nav-bg-height: calc(20em + calc(2em + 3em + 2.5em + 3em));
--cubic-default: cubic-bezier(0.525, 0, 0, 1);
--duration-fast: 0.2s;
--duration-normal: 0.450s;
--color-dark: #2b1d15;
}
.nav {
z-index: 100;
color: #fff;
position: fixed;
top: 0;
left: 0;
right: 0;
transition: color var(--duration-fast) var(--cubic-default);
}
.nav-inner {
justify-content: space-between;
align-items: center;
width: 100%;
padding-top: 2em;
display: flex;
position: relative;
}
.nav-container {
z-index: 1;
width: 100%;
margin-left: auto;
margin-right: auto;
padding-left: 3em;
padding-right: 3em;
position: relative;
}
.nav-logo {
width: 6.75em;
}
.nav-end {
grid-column-gap: .75em;
grid-row-gap: .75em;
justify-content: flex-end;
align-items: stretch;
display: flex;
}
.nav-button {
border: 1px solid;
border-radius: .25em;
justify-content: center;
align-items: center;
height: 3em;
padding-left: 1em;
padding-right: 1em;
font-size: 1em;
line-height: 1.4;
display: flex;
transition: all var(--duration-fast) var(--cubic-default);
}
.nav-button.is--primary {
color: #2b1d15;
background-color: #fff;
border-color: #fff;
}
.nav-center__list {
justify-content: center;
align-items: center;
margin-bottom: 0;
padding-left: 0;
list-style: none;
display: flex;
}
.nav-link {
grid-column-gap: .375em;
grid-row-gap: .375em;
background-color: #0000;
border-radius: .25em;
justify-content: center;
align-items: center;
padding: .75em 1.25em;
display: flex;
}
.nav-link__label {
font-size: 1.25em;
line-height: 1;
position: relative;
}
.nav-link__dropdown-icon {
width: .875em;
}
.nav-dropdown {
opacity: 0;
pointer-events: none;
visibility: hidden;
width: 100%;
padding-top: 2.5em;
padding-bottom: 3em;
position: fixed;
left: 0;
right: 0;
transition: all var(--duration-fast) ease, transform var(--duration-normal) var(--cubic-default);
}
.nav-dropdown.visible {
opacity: 100;
pointer-events: auto;
visibility: visible;
}
.nav-bg {
background-color: #fff;
border-bottom-right-radius: .75em;
border-bottom-left-radius: .75em;
width: 100%;
height: 0;
position: absolute;
inset: 0% 0% auto;
transition: height var(--duration-normal) var(--cubic-default);
}
.nav-dropdown__content {
grid-column-gap: 1.25em;
grid-row-gap: 1.25em;
width: 100%;
margin-bottom: 0;
padding-left: 0;
list-style: none;
display: flex;
}
.nav-dropdown__content-li {
flex: 1;
}
.nav-dropdown__link {
color: #fff;
background-color: #ebe7e4;
border-radius: .25em;
flex-flow: column;
flex: 1;
justify-content: flex-end;
align-items: stretch;
height: 20em;
padding: 1.5em;
display: flex;
position: relative;
overflow: hidden;
}
.nav-dropdown__link.is--static {
color: #2b1d15;
transition: background-color .2s cubic-bezier(.625, .05, 0, 1);
}
.nav-dropdown__link-inner {
z-index: 1;
justify-content: space-between;
align-items: center;
display: flex;
position: relative;
}
.nav-dropdown__link-label {
font-size: 1.75em;
transition: transform .2s cubic-bezier(.625, .05, 0, 1);
}
.nav-dropdown__link-bubble {
color: #fff;
background-color: #2b1d15;
border-radius: 100em;
justify-content: center;
align-items: center;
width: 1.5em;
height: 1.5em;
padding: .25em;
transition: transform .2s cubic-bezier(.625, .05, 0, 1);
display: flex;
}
.icon {
width: 100%;
height: 100%;
}
.nav-dropdown__link-bg {
position: absolute;
inset: 0%;
}
.nav-dropdown__img {
object-fit: cover;
width: 100%;
height: 100%;
transition: transform .35s cubic-bezier(.625, .05, 0, 1);
}
.nav-dropdown__img-overlay {
z-index: 1;
opacity: .25;
background-image: linear-gradient(#0000, #0000 50%, #000);
transition: opacity .2s cubic-bezier(.625, .05, 0, 1);
position: absolute;
inset: 0%;
}
.page-bg {
z-index: 0;
opacity: 0;
pointer-events: none;
background-color: #0000004d;
width: 100%;
height: 100vh;
position: absolute;
inset: 0% 0% auto;
transition: opacity var(--duration-fast) var(--cubic-default);
}
.menu-button {
grid-column-gap: .25em;
grid-row-gap: .25em;
background-color: currentColor;
border-radius: .25em;
flex-flow: column;
justify-content: center;
align-items: center;
width: 3em;
height: 3em;
padding: .75em;
display: none;
}
.menu-button.is--primary {
color: #2b1d15;
background-color: #fff;
border-color: #fff;
}
.menu-button__line {
background-color: #2b1d15;
width: 100%;
height: 1px;
}
/* Show dropdown */
[data-dropdown-toggle]:hover + .nav-dropdown,
[data-dropdown-toggle]:focus-visible + .nav-dropdown,
.nav-dropdown:hover,
.nav-dropdown:focus-within {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
/* Styling when dropdown is open */
:is(
body:has([data-dropdown-toggle]:hover),
body:has([data-dropdown-toggle]:focus-visible),
body:has([data-dropdown-toggle]:focus-within),
body:has(.nav-dropdown:hover),
body:has(.nav-dropdown:focus-within)
) {
.nav-bg { height: var(--nav-bg-height); }
.page-bg { opacity: 1; }
.nav { color: var(--color-dark); }
.nav-button { border-color: var(--color-dark); color: var(--color-dark); }
.nav-button.is--primary { background-color: var(--color-dark); border-color: var(--color-dark); color: #FFF; }
}
/* Dropdown toggle chevron */
.nav-link__dropdown-icon {
transition: transform var(--duration-normal) var(--cubic-default);
}
[data-dropdown-toggle] {
transition: background-color var(--duration-fast) var(--cubic-default);
}
/* Desktop hover and focus */
@media screen and (min-width: 768px) {
[data-dropdown-toggle]:hover .nav-link__dropdown-icon,
[data-dropdown-toggle]:focus .nav-link__dropdown-icon,
[data-dropdown-toggle]:focus-within .nav-link__dropdown-icon,
[data-dropdown-toggle]:has(+ .nav-dropdown:hover) .nav-link__dropdown-icon,
[data-dropdown-toggle]:has(+ .nav-dropdown:focus-within) .nav-link__dropdown-icon {
transform: rotate(180deg);
}
[data-dropdown-toggle]:hover,
[data-dropdown-toggle]:focus,
[data-dropdown-toggle]:focus-within,
[data-dropdown-toggle]:has(+ .nav-dropdown:hover),
[data-dropdown-toggle]:has(+ .nav-dropdown:focus-within) {
background-color: #EBE7E4;
}
}
/* Dropdown content list items */
.nav-dropdown__content-li {
transition: all var(--duration-normal) var(--cubic-default);
transition-delay: 0.18s;
opacity: 0;
transform: translate(4em, 0px);
}
.nav-dropdown__content-li:nth-child(2) { transition-delay: 0.24s; }
.nav-dropdown__content-li:nth-child(3) { transition-delay: 0.3s; }
.nav-dropdown__content-li:nth-child(4) { transition-delay: 0.36s; }
.nav-dropdown__content-li:nth-child(5) { transition-delay: 0.44s; }
body:has([data-dropdown-toggle]:hover) [data-dropdown-toggle]:hover + .nav-dropdown .nav-dropdown__content-li,
body:has([data-dropdown-toggle]:focus-visible) .nav-dropdown__content-li,
body:has([data-dropdown-toggle]:focus-within) .nav-dropdown__content-li,
body:has(.nav-dropdown:hover) .nav-dropdown__content-li,
body:has(.nav-dropdown:focus-within) .nav-dropdown__content-li {
opacity: 1;
transform: translate(0em, 0px);
}
/* Dropdown link images */
.nav-dropdown__link:hover .nav-dropdown__img-overlay,
.nav-dropdown__link:focus-visible .nav-dropdown__img-overlay {
opacity: 0;
}
.nav-dropdown__link:hover .nav-dropdown__img,
.nav-dropdown__link:focus-visible .nav-dropdown__img {
transform: scale(1.1);
}
/* Static dropdown links */
.nav-dropdown__link.is--static:hover,
.nav-dropdown__link.is--static:focus-visible {
background: #D7D1CD;
}
/* Plain nav links underline */
a.nav-link .nav-link__label::after {
content: '';
position: absolute;
left: 0;
bottom: -1px;
width: 100%;
height: 1px;
background: currentColor;
transition: transform var(--duration-normal) var(--cubic-default);
transform: scale(0, 1);
transform-origin: right center;
}
a.nav-link:hover .nav-link__label::after,
a.nav-link:focus-visible .nav-link__label::after {
transform: scale(1, 1);
transform-origin: left center;
}
@media screen and (max-width: 991px) {
.nav-inner { padding-top: 1.25em; }
.nav-container { padding-left: 1.25em; padding-right: 1.25em; }
.nav-button.md--hide { display: none; }
.nav-link { padding-left: 1em; padding-right: 1em; }
.nav-link__label { font-size: 1em; }
.md--hide, .menu-button.md--hide { display: none; }
}
@media screen and (max-width: 767px) {
.nav-logo { z-index: 1; position: relative; }
.nav-end { z-index: 2; position: relative; }
.nav-center {
z-index: 0;
opacity: 0;
visibility: hidden;
height: 100dvh;
position: absolute;
top: 0;
left: -1.25em;
right: -1.25em;
transform: translate(0, 1em);
}
.nav-center__list {
flex-flow: column;
justify-content: flex-start;
align-items: stretch;
height: 100%;
padding-top: 8em;
overflow: hidden scroll;
}
.nav-link {
justify-content: space-between;
align-items: center;
width: 100%;
padding: 1em 1.25em;
}
.nav-link__label { font-size: 2em; }
.nav-dropdown {
opacity: 100;
pointer-events: auto;
visibility: visible;
padding-top: 0;
padding-bottom: 0;
position: relative;
inset: auto;
}
.nav-bg {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
height: 0;
}
.nav-dropdown__overflow {
grid-template-rows: 0fr;
width: 100%;
display: grid;
position: relative;
overflow: hidden;
}
.nav-dropdown__overflow-inner {
flex-flow: column;
width: 100%;
height: 1000000%;
display: flex;
position: relative;
overflow: hidden;
}
.nav-dropdown__content {
grid-gap: .75em;
grid-template-columns: repeat(2, 1fr);
padding-top: 1em;
padding-bottom: 1em;
display: grid;
}
.nav-dropdown__link {
aspect-ratio: 1;
height: auto;
padding: 1em;
}
.nav-dropdown__link-label { font-size: 1.25em; }
.sm--hide { display: none; }
.menu-button { display: flex; }
:root { --nav-bg-height: 100dvh; }
.nav-dropdown__overflow {
transition: grid-template-rows var(--duration-normal) var(--cubic-default);
}
.nav-center {
transition: all var(--duration-normal) var(--cubic-default), opacity var(--duration-fast) var(--cubic-default);
}
.menu-button__line {
transition: all var(--duration-normal) var(--cubic-default);
}
/* Styles when menu is open */
[data-menu-status="open"] {
color: var(--color-dark);
.menu-button__line:nth-of-type(1) {
transform: translate(0px, 0.125em) rotate(135deg);
background-color: #FFF;
}
.menu-button__line:nth-of-type(2) {
transform: translate(0px, -0.175em) rotate(-135deg);
background-color: #FFF;
}
.nav-bg { height: var(--nav-bg-height); }
.page-bg { opacity: 1; }
.nav-button.is--primary { background-color: var(--color-dark); border-color: var(--color-dark); color: #FFF; }
.nav-center {
opacity: 1;
visibility: visible;
transform: translate(0px, 0em);
transition-delay: 0.1s;
}
}
[data-dropdown-toggle="open"] + .nav-dropdown .nav-dropdown__overflow {
grid-template-rows: 1fr;
}
[data-dropdown-toggle="open"] + .nav-dropdown .nav-dropdown__content-li {
opacity: 1;
transform: translate(0em, 0px);
}
[data-dropdown-toggle="open"] .nav-link__dropdown-icon {
transform: rotate(180deg);
}
}
@media screen and (max-width: 479px) {
.nav-logo { width: 5em; }
.nav-end { grid-column-gap: .5em; grid-row-gap: .5em; }
.nav-button.is--primary { height: 2.5em; padding-left: .75em; padding-right: .75em; }
.nav-dropdown__link { padding: .75em; }
.nav-dropdown__link-label { font-size: 1em; }
.nav-dropdown__link-bubble { width: 1.25em; height: 1.25em; padding: .375em; }
.xs--hide { display: none; }
.menu-button { width: 2.5em; height: 2.5em; }
}function initNavigation() {
if (!initNavigation._hasResizeListener) {
initNavigation._hasResizeListener = true;
window.addEventListener('resize', debounce(initNavigation, 200));
}
const isMobile = window.innerWidth < 768;
if (isMobile && initNavigation._lastMode !== 'mobile') {
initMobileMenu();
initNavigation._lastMode = 'mobile';
} else if (!isMobile && initNavigation._lastMode !== 'desktop') {
initDesktopDropdowns();
initNavigation._lastMode = 'desktop';
}
}
function debounce(fn, delay) {
let timer;
return () => {
clearTimeout(timer);
timer = setTimeout(fn, delay);
};
}
function initMobileMenu() {
const btn = document.querySelector('[data-menu-button]');
const nav = document.querySelector('[data-menu-status]');
if (!btn || !nav) return;
btn.setAttribute('aria-expanded', 'false');
btn.setAttribute('aria-controls', 'mobile-navigation');
nav.setAttribute('id', 'mobile-navigation');
nav.setAttribute('role', 'navigation');
nav.setAttribute('aria-label', 'Main navigation');
if (!btn._mobileClick) {
btn._mobileClick = true;
btn.addEventListener('click', () => {
const open = nav.dataset.menuStatus === 'open';
nav.dataset.menuStatus = open ? 'closed' : 'open';
btn.setAttribute('aria-expanded', !open);
if (open) {
Array.from(document.querySelectorAll('[data-dropdown-toggle]')).forEach(toggle => {
toggle.dataset.dropdownToggle = 'closed';
toggle.setAttribute('aria-expanded', 'false');
});
}
});
}
Array.from(document.querySelectorAll('[data-dropdown-toggle]')).forEach((toggle, i) => {
const dd = toggle.nextElementSibling;
if (!dd || !dd.classList.contains('nav-dropdown')) return;
if (toggle._mobileDropdownInit) return;
toggle._mobileDropdownInit = true;
toggle.setAttribute('aria-expanded', 'false');
toggle.setAttribute('aria-haspopup', 'true');
toggle.setAttribute('aria-controls', `dropdown-${i}`);
dd.setAttribute('id', `dropdown-${i}`);
dd.setAttribute('role', 'menu');
dd.querySelectorAll('.nav-dropdown__link').forEach(link => link.setAttribute('role', 'menuitem'));
toggle.addEventListener('click', () => {
const open = toggle.dataset.dropdownToggle === 'open';
Array.from(document.querySelectorAll('[data-dropdown-toggle]')).forEach(other => {
if (other !== toggle) {
other.dataset.dropdownToggle = 'closed';
other.setAttribute('aria-expanded', 'false');
}
});
toggle.dataset.dropdownToggle = open ? 'closed' : 'open';
toggle.setAttribute('aria-expanded', !open);
});
});
}
function initDesktopDropdowns() {
const toggles = Array.from(document.querySelectorAll('[data-dropdown-toggle]'));
const links = Array.from(document.querySelectorAll('.nav-link:not([data-dropdown-toggle])'));
toggles.forEach((toggle, i) => {
const dd = toggle.nextElementSibling;
if (!dd || !dd.classList.contains('nav-dropdown') || toggle._desktopInit) return;
toggle._desktopInit = true;
toggle.setAttribute('aria-expanded', 'false');
toggle.setAttribute('aria-haspopup', 'true');
toggle.setAttribute('aria-controls', `desktop-dropdown-${i}`);
dd.setAttribute('id', `desktop-dropdown-${i}`);
dd.setAttribute('role', 'menu');
dd.setAttribute('aria-hidden', 'true');
dd.querySelectorAll('.nav-dropdown__link').forEach(link => link.setAttribute('role', 'menuitem'));
toggle.addEventListener('mouseenter', () => {
const anyOpen = toggles.some(x => x.dataset.dropdownToggle === 'open');
toggles.forEach(other => {
if (other !== toggle) {
other.dataset.dropdownToggle = 'closed';
other.setAttribute('aria-expanded', 'false');
const otherDd = other.nextElementSibling;
if (otherDd) otherDd.setAttribute('aria-hidden', 'true');
}
});
if (anyOpen) {
setTimeout(() => {
toggle.dataset.dropdownToggle = 'open';
toggle.setAttribute('aria-expanded', 'true');
dd.setAttribute('aria-hidden', 'false');
}, 20);
} else {
toggle.dataset.dropdownToggle = 'open';
toggle.setAttribute('aria-expanded', 'true');
dd.setAttribute('aria-hidden', 'false');
}
});
dd.addEventListener('mouseleave', () => {
toggle.dataset.dropdownToggle = 'closed';
toggle.setAttribute('aria-expanded', 'false');
dd.setAttribute('aria-hidden', 'true');
});
toggle.addEventListener('keydown', e => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggle.click();
} else if (e.key === 'Escape') {
toggle.dataset.dropdownToggle = 'closed';
toggle.setAttribute('aria-expanded', 'false');
dd.setAttribute('aria-hidden', 'true');
toggle.focus();
}
});
dd.addEventListener('keydown', e => {
const items = Array.from(dd.querySelectorAll('.nav-dropdown__link'));
const idx = items.indexOf(document.activeElement);
if (e.key === 'ArrowDown') {
e.preventDefault();
items[(idx + 1) % items.length].focus();
} else if (e.key === 'ArrowUp') {
e.preventDefault();
items[(idx - 1 + items.length) % items.length].focus();
} else if (e.key === 'Escape') {
e.preventDefault();
toggle.dataset.dropdownToggle = 'closed';
toggle.setAttribute('aria-expanded', 'false');
dd.setAttribute('aria-hidden', 'true');
toggle.focus();
}
});
});
links.forEach(link => {
link.addEventListener('mouseenter', () => {
toggles.forEach(toggle => {
toggle.dataset.dropdownToggle = 'closed';
toggle.setAttribute('aria-expanded', 'false');
const dd = toggle.nextElementSibling;
if (dd) dd.setAttribute('aria-hidden', 'true');
});
});
});
document.addEventListener('click', e => {
const inside = toggles.some(toggle => {
const dd = toggle.nextElementSibling;
return toggle.contains(e.target) || (dd && dd.contains(e.target));
});
if (!inside) {
toggles.forEach(toggle => {
toggle.dataset.dropdownToggle = 'closed';
toggle.setAttribute('aria-expanded', 'false');
const dd = toggle.nextElementSibling;
if (dd) dd.setAttribute('aria-hidden', 'true');
});
}
});
}
document.addEventListener('DOMContentLoaded', () => {
initNavigation();
});Attributes
| Name | Type | Default | Description |
|---|---|---|---|
| data-menu-status | "open" | "closed" | "closed" | Placed on the <nav> element. The script flips this to "open" when the burger button is pressed on mobile, triggering the full-screen menu CSS. |
| data-menu-button | attribute | — | Placed on the burger/hamburger button. The script listens for clicks on this element to toggle data-menu-status. |
| data-dropdown-toggle | "" | "open" | "closed" | "" | Placed on <button> elements that open a dropdown. On desktop, hover/focus drives the CSS. On mobile, the script sets this to "open" to expand the accordion. Set to "open" in the designer to preview dropdown content. |
Notes
- •No GSAP required — all animation is CSS transitions.
- •The mobile breakpoint is 768px in both the CSS media queries and the JS width check — keep them in sync if you change it.
- •Set data-dropdown-toggle="open" on any toggle in the designer/editor to preview its dropdown content without running JS.
- •Keyboard navigation is fully supported on desktop: Enter/Space open, Escape closes, ArrowDown/Up move focus within the panel.
- •ARIA attributes (aria-expanded, aria-haspopup, role, aria-controls) are injected by JS at runtime so they reflect the actual interactive mode.
Guide
Desktop vs mobile modes
The script detects viewport width on load and resize (200ms debounce). Below 768px it runs initMobileMenu(); at 768px and above it runs initDesktopDropdowns(). Both modes are mutually exclusive and use guard flags (_mobileDropdownInit, _desktopInit) to avoid re-binding listeners.
Desktop dropdowns
On desktop, dropdowns are shown/hidden purely by CSS :hover and :focus-visible selectors — no JS needed for the visual state. JS only adds ARIA attributes (aria-expanded, aria-haspopup, role="menu") and handles keyboard navigation (Enter/Space open, Escape closes, ArrowDown/Up move focus between items).
Mobile accordion
On mobile, the center nav slides in as a full-screen overlay when data-menu-status="open". Each dropdown toggle controls a grid-template-rows: 0fr → 1fr accordion via [data-dropdown-toggle="open"]. Opening one accordion auto-closes the others.
Nav background height
The white background panel (.nav-bg) grows to --nav-bg-height when a dropdown is open. The default value accounts for the 20em card height plus internal spacing. Adjust this custom property if you change card dimensions or padding.
:root {
--nav-bg-height: calc(20em + calc(2em + 3em + 2.5em + 3em));
}Static vs image links
Dropdown items with .nav-dropdown__link have an image background with a zoom-on-hover effect and gradient overlay. Add .is--static to remove the image layout and switch to a plain text link with a solid hover background.