<header class="dropdown-nav" aria-label="Main Menu Navigation">
<div class="dropdown-nav__inner">
<nav class="dropdown-nav__menu" role="navigation">
<div class="dropdown-nav__toggle-container">
<button class="btn dropdown-nav__toggle" aria-haspopup="true" aria-expanded="false" aria-controls="main-menu" data-nav-toggle>Menu</button>
</div>
<div class="dropdown-nav__list-container" data-nav-list-container>
<ul class="dropdown-nav__list" id="main-menu" aria-label="Main Menu Items" data-nav-list>
<li class="dropdown-nav__item" data-dropdown-item>
<a href="/" class="dropdown-nav__link" aria-controls="lorem-submenu" data-dropdown-link data-has-popup>
Lorem
<svg class="dropdown-nav__arrow" viewBox="0 0 407.437 407.437" role="presentation" aria-hidden="true">
<polygon points="386.258,91.567 203.718,273.512 21.179,91.567 0,112.815 203.718,315.87 407.437,112.815 "/>
</svg>
</a>
<button class="sub-nav__toggle" aria-controls="lorem-submenu" data-subnav-toggle>
<span class="u-sr-only">Toggle Subnav</span>
<svg class="sub-nav__arrow" viewBox="0 0 407.437 407.437" role="presentation" aria-hidden="true">
<polygon points="386.258,91.567 203.718,273.512 21.179,91.567 0,112.815 203.718,315.87 407.437,112.815 "/>
</svg>
</button>
<div class="dropdown-nav__sub-list-container" data-sub-list-container>
<ul class="dropdown-nav__sub-list" id="lorem-submenu" aria-label="Lorem Submenu" data-sub-list>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Lorem</a>
</li>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Ipsum</a>
</li>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Dolar</a>
</li>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Set</a>
</li>
</ul>
</div>
</li>
<li class="dropdown-nav__item" data-dropdown-item>
<a href="/" class="dropdown-nav__link" data-dropdown-link>Ipsum</a>
</li>
<li class="dropdown-nav__item" data-dropdown-item>
<a href="/" class="dropdown-nav__link" data-dropdown-link>Dolar</a>
</li>
<li class="dropdown-nav__item" data-dropdown-item>
<a href="/" class="dropdown-nav__link" aria-controls="amet-submenu" data-dropdown-link data-has-popup>Amet
<svg class="dropdown-nav__arrow" viewBox="0 0 407.437 407.437" role="presentation" aria-hidden="true">
<polygon points="386.258,91.567 203.718,273.512 21.179,91.567 0,112.815 203.718,315.87 407.437,112.815 "/>
</svg>
</a>
<button class="sub-nav__toggle" aria-controls="amet-submenu" data-subnav-toggle>
<span class="u-sr-only">Toggle Subnav</span>
<svg class="sub-nav__arrow" viewBox="0 0 407.437 407.437" role="presentation" aria-hidden="true">
<polygon points="386.258,91.567 203.718,273.512 21.179,91.567 0,112.815 203.718,315.87 407.437,112.815 "/>
</svg>
</button>
<div class="dropdown-nav__sub-list-container" data-sub-list-container>
<ul class="dropdown-nav__sub-list" id="amet-submenu" aria-label="Amet Submenu" data-sub-list>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Lorem</a>
</li>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Ipsum</a>
</li>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Dolar</a>
</li>
</ul>
</div>
</li>
<li class="dropdown-nav__item" data-dropdown-item>
<a href="/" class="dropdown-nav__link" aria-controls="faucibus-submenu" data-dropdown-link data-has-popup>Faucibus
<svg class="dropdown-nav__arrow" viewBox="0 0 407.437 407.437" role="presentation" aria-hidden="true">
<polygon points="386.258,91.567 203.718,273.512 21.179,91.567 0,112.815 203.718,315.87 407.437,112.815 "/>
</svg>
</a>
<button class="sub-nav__toggle" aria-controls="faucibus-submenu" data-subnav-toggle>
<span class="u-sr-only">Toggle Subnav</span>
<svg class="sub-nav__arrow" viewBox="0 0 407.437 407.437" role="presentation" aria-hidden="true">
<polygon points="386.258,91.567 203.718,273.512 21.179,91.567 0,112.815 203.718,315.87 407.437,112.815 "/>
</svg>
</button>
<div class="dropdown-nav__sub-list-container" data-sub-list-container>
<ul class="dropdown-nav__sub-list" id="faucibus-submenu" aria-label="Faucibus Submenu" data-sub-list>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Ipsum</a>
</li>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Dolar</a>
</li>
</ul>
</div>
</li>
</ul>
</div>
<!-- /.container -->
</nav>
</div>
</header>
<header class="dropdown-nav" aria-label="Main Menu Navigation">
<div class="dropdown-nav__inner">
<nav class="dropdown-nav__menu" role="navigation">
<div class="dropdown-nav__toggle-container">
<button class="btn dropdown-nav__toggle" aria-haspopup="true" aria-expanded="false" aria-controls="main-menu" data-nav-toggle>Menu</button>
</div>
<div class="dropdown-nav__list-container" data-nav-list-container>
<ul class="dropdown-nav__list" id="main-menu" aria-label="Main Menu Items" data-nav-list>
<li class="dropdown-nav__item" data-dropdown-item>
<a href="/" class="dropdown-nav__link" aria-controls="lorem-submenu" data-dropdown-link data-has-popup>
Lorem
<svg class="dropdown-nav__arrow" viewBox="0 0 407.437 407.437" role="presentation" aria-hidden="true">
<polygon points="386.258,91.567 203.718,273.512 21.179,91.567 0,112.815 203.718,315.87 407.437,112.815 "/>
</svg>
</a>
<button class="sub-nav__toggle" aria-controls="lorem-submenu" data-subnav-toggle>
<span class="u-sr-only">Toggle Subnav</span>
<svg class="sub-nav__arrow" viewBox="0 0 407.437 407.437" role="presentation" aria-hidden="true">
<polygon points="386.258,91.567 203.718,273.512 21.179,91.567 0,112.815 203.718,315.87 407.437,112.815 "/>
</svg>
</button>
<div class="dropdown-nav__sub-list-container" data-sub-list-container>
<ul class="dropdown-nav__sub-list" id="lorem-submenu" aria-label="Lorem Submenu" data-sub-list>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Lorem</a>
</li>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Ipsum</a>
</li>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Dolar</a>
</li>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Set</a>
</li>
</ul>
</div>
</li>
<li class="dropdown-nav__item" data-dropdown-item>
<a href="/" class="dropdown-nav__link" data-dropdown-link>Ipsum</a>
</li>
<li class="dropdown-nav__item" data-dropdown-item>
<a href="/" class="dropdown-nav__link" data-dropdown-link>Dolar</a>
</li>
<li class="dropdown-nav__item" data-dropdown-item>
<a href="/" class="dropdown-nav__link" aria-controls="amet-submenu" data-dropdown-link data-has-popup>Amet
<svg class="dropdown-nav__arrow" viewBox="0 0 407.437 407.437" role="presentation" aria-hidden="true">
<polygon points="386.258,91.567 203.718,273.512 21.179,91.567 0,112.815 203.718,315.87 407.437,112.815 "/>
</svg>
</a>
<button class="sub-nav__toggle" aria-controls="amet-submenu" data-subnav-toggle>
<span class="u-sr-only">Toggle Subnav</span>
<svg class="sub-nav__arrow" viewBox="0 0 407.437 407.437" role="presentation" aria-hidden="true">
<polygon points="386.258,91.567 203.718,273.512 21.179,91.567 0,112.815 203.718,315.87 407.437,112.815 "/>
</svg>
</button>
<div class="dropdown-nav__sub-list-container" data-sub-list-container>
<ul class="dropdown-nav__sub-list" id="amet-submenu" aria-label="Amet Submenu" data-sub-list>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Lorem</a>
</li>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Ipsum</a>
</li>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Dolar</a>
</li>
</ul>
</div>
</li>
<li class="dropdown-nav__item" data-dropdown-item>
<a href="/" class="dropdown-nav__link" aria-controls="faucibus-submenu"data-dropdown-link data-has-popup>Faucibus
<svg class="dropdown-nav__arrow" viewBox="0 0 407.437 407.437" role="presentation" aria-hidden="true">
<polygon points="386.258,91.567 203.718,273.512 21.179,91.567 0,112.815 203.718,315.87 407.437,112.815 "/>
</svg>
</a>
<button class="sub-nav__toggle" aria-controls="faucibus-submenu" data-subnav-toggle>
<span class="u-sr-only">Toggle Subnav</span>
<svg class="sub-nav__arrow" viewBox="0 0 407.437 407.437" role="presentation" aria-hidden="true">
<polygon points="386.258,91.567 203.718,273.512 21.179,91.567 0,112.815 203.718,315.87 407.437,112.815 "/>
</svg>
</button>
<div class="dropdown-nav__sub-list-container" data-sub-list-container>
<ul class="dropdown-nav__sub-list" id="faucibus-submenu" aria-label="Faucibus Submenu" data-sub-list>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Ipsum</a>
</li>
<li class="dropdown-nav__sub-item">
<a href="/" class="dropdown-nav__sub-link">Dolar</a>
</li>
</ul>
</div>
</li>
</ul>
</div>
<!-- /.container -->
</nav>
</div>
</header>
/* No context defined for this component. */
.dropdown-nav {
border-bottom: 1px solid color(neutral, lighter);
background-color: color(neutral, lightest);
&__inner {
display: flex;
margin: 0 auto;
padding: 0;
width: 100%;
max-width: 1200px;
}
&__menu {
width: 100%;
@media (min-width: $breakpoint-md) {
margin: 0 auto;
width: auto;
}
}
&__toggle-container {
display: flex;
@media (min-width: $breakpoint-md) { display: none; }
}
&__toggle {
margin-left: auto;
border-radius: 0;
background-color: transparent;
color: $text-color;
&:focus { z-index: 1; } // to see full focus outline
}
&__list-container {
margin: 0;
padding: 0;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
transition: height .2s ease-in-out, visibility .2s ease-in-out;
@media (min-width: $breakpoint-md) {
height: auto;
overflow: visible;
visibility: visible;
}
&.is-open {
height: 100vh;
overflow: auto;
visibility: visible;
}
}
&__list {
margin: 0;
padding: 0;
width: 100%;
overflow: hidden;
transition: height .2s ease-in-out, visibility .2s ease-in-out;
@media (min-width: $breakpoint-md) {
margin: 0 auto;
width: auto;
height: auto;
overflow: visible;
}
}
&__item {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
margin: 0;
border-top: 1px solid color(neutral, lighter);
border-right: 1px solid color(neutral, lighter);
border-left: 1px solid color(neutral, lighter);
background-color: color(neutral, lightest);
&:last-child {
border-bottom: 1px solid color(neutral, lighter);
@media (min-width: $breakpoint-md) { border: 0; }
}
@media (min-width: $breakpoint-md) {
display: inline-block;
border: 0;
padding: $base-spacing 0;
&:focus,
&:focus-within,
&:hover,
&.is-hovered {
&__sub-list-container {
opacity: 1;
visibility: visible;
}
}
}
.sub-nav__toggle {
display: block;
border: 0;
border-left: 1px solid color(neutral, lighter);
padding: 20px;
background-color: color(neutral, lightest);
&:focus { z-index: 1; } // to see full focus outline
@media (min-width: $breakpoint-md) { display: none; }
}
.sub-nav__arrow {
display: block;
width: 20px;
height: 20px;
pointer-events: none;
transition: .2s ease-in-out;
&.is-active { transform: rotate(180deg); }
}
}
&__item + &__item {
list-style: none;
@media (min-width: $breakpoint-md) { margin-left: 40px; }
}
&__link {
@include font-size(18);
display: flex;
flex: 1;
align-items: center;
justify-content: space-between;
padding: 15px 0 15px 15px;
width: auto;
@media (min-width: $breakpoint-md) {
display: inline-flex;
line-height: 1;
}
}
&__link.is-open + button + &__sub-list-container {
opacity: 1;
visibility: visible;
}
&__sub-list-container {
margin: 0;
padding: 0;
width: 100%;
height: 0;
overflow: hidden;
transition: height .2s ease-in-out, visibility .2s ease-in-out;
@media (min-width: $breakpoint-md) {
position: absolute;
top: 100%;
left: 0;
display: block;
margin: 0;
border: 1px solid #dcdcdc;
border-top: 0;
width: 180px;
height: auto;
overflow: visible;
background-color: #f4f4f4;
opacity: 0;
visibility: hidden;
}
&.is-open { height: auto; }
}
&__sub-list {
display: flex;
flex-direction: column;
margin: 0;
padding: 0;
width: 100%;
overflow: hidden;
background-color: color(neutral, lightest);
transition: height .2s ease-in-out, visibility .2s ease-in-out;
@media (min-width: $breakpoint-md) {
margin: 0;
border-top: 0;
padding: 10px;
height: auto;
max-height: none;
background-color: color(neutral, lightest);
visibility: visible;
transition: none;
}
.is-open & { border-top: 1px solid color(neutral, lighter); }
}
&__sub-item {
@include font-size(16);
display: block;
margin: 0;
border-bottom: 1px solid color(neutral, lighter);
padding: 15px 0 15px 60px;
background-color: color(neutral, lightest);
list-style: none;
&:last-child { border-bottom: 0; }
@media (min-width: $breakpoint-md) {
border: 0;
padding: 0;
}
}
&__sub-item + &__sub-item {
@media (min-width: $breakpoint-md) { margin-top: 10px; }
}
&__arrow {
display: none;
pointer-events: none;
transition: .2s ease-in-out;
@media (min-width: $breakpoint-md) {
display: block;
margin-right: 0;
margin-left: 10px;
width: 10px;
height: 10px;
}
}
}
const mobileBreakpointMax = 768;
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
const dropdownItems = document.querySelectorAll('[data-dropdown-item]');
const navContainer = document.querySelector('[data-nav-list-container]');
const mainMenuToggle = document.querySelector('[data-nav-toggle]');
const mainMenu = document.querySelector('[data-nav-list]');
const subNavContainers = document.querySelectorAll('[data-sub-list-container]');
const subNavContainer = document.querySelector('[data-sub-list-container]');
const subMenuToggles = document.querySelectorAll('[data-subnav-toggle]');
const subMenuLinks = document.querySelectorAll('[data-has-popup]');
const subMenuArrows = document.querySelectorAll('.sub-nav__arrow');
const hoveredClassName = 'is-hovered';
// set aria attributes on buttons on mobile, links on desktop
function setAriaAttributes() {
if (windowWidth < mobileBreakpointMax) {
for (let i = 0; i < subMenuLinks.length; i++) {
subMenuLinks[i].removeAttribute('aria-has-popup', 'false');
subMenuLinks[i].removeAttribute('aria-expanded', 'false');
}
for (let i = 0; i < subMenuToggles.length; i++) {
subMenuToggles[i].setAttribute('aria-has-popup', 'true');
subMenuToggles[i].setAttribute('aria-expanded', 'false');
}
} else {
for (let i = 0; i < subMenuLinks.length; i++) {
subMenuLinks[i].setAttribute('aria-has-popup', 'true');
subMenuLinks[i].setAttribute('aria-expanded', 'false');
}
for (let i = 0; i < subMenuToggles.length; i++) {
subMenuToggles[i].removeAttribute('aria-has-popup', 'false');
subMenuToggles[i].removeAttribute('aria-expanded', 'false');
}
}
}
function handleMobileNav() {
// open/close top level menu on mobile
mainMenuToggle.addEventListener('click', function(e) {
const toggleExpanded = (e.target.getAttribute('aria-expanded') === 'true') ? false : true;
mainMenuToggle.setAttribute('aria-expanded', toggleExpanded);
if (navContainer.classList.contains('is-open')) {
navContainer.classList.remove('is-open');
//when main menu is closed, also close all submenus
for (let i = 0; i < subMenuToggles.length; i++) {
if (subMenuToggles[i].classList.contains('is-open')) {
subNavContainer.classList.remove('is-open');
}
}
} else {
navContainer.classList.add('is-open');
}
});
// open/close sub-level menus on mobile using buttons
for (let i = 0; i < subMenuToggles.length; i++) {
subMenuToggles[i].addEventListener('click', function(e) {
const subNavContainer = subMenuToggles[i].nextElementSibling;
const subNavParent = subNavContainer.parentElement;
const subMenuHeight = e.target.nextElementSibling.firstElementChild.offsetHeight;
const subToggleIcon = e.target.querySelector('.sub-nav__arrow');
// rotate arrow icon
subToggleIcon.classList.add('is-active');
if(subNavParent.classList.contains('is-open')) {
subNavParent.classList.remove('is-open');
subToggleIcon.classList.remove('is-active');
subNavContainer.setAttribute('style', 'height: 0; visibility: hidden;');
subMenuToggles[i].setAttribute('aria-expanded', 'false');
} else {
subNavParent.classList.add('is-open');
subToggleIcon.classList.add('is-active');
subNavContainer.setAttribute('style', `height: ${subMenuHeight}px; visibility: visible;`);
subMenuToggles[i].setAttribute('aria-expanded', 'true');
}
});
}
};
// handle touchscreens in landscape orientation where desktop version of menu is displayed
function handleTabletTouch() {
for (let i = 0; i < subMenuLinks.length; i++) {
subMenuLinks[i].addEventListener('touchstart', function(e) {
const currentTarget = e.currentTarget;
const previousCurrentTarget = document.querySelectorAll('.is-hovered');
currentTarget.parentElement.classList.add('is-hovered');
currentTarget.setAttribute('aria-expanded', 'true');
for (let i = 0; i < previousCurrentTarget.length; i++) {
previousCurrentTarget[i].classList.remove('is-hovered');
previousCurrentTarget[i].firstElementChild.setAttribute('aria-expanded', 'false');
}
e.preventDefault();
});
}
}
function collapseSubNav() {
if (navContainer.classList.contains('is-open')) {
navContainer.classList.remove('is-open');
}
for (let i = 0; i < dropdownItems.length; i++) {
dropdownItems[i].classList.remove('is-open');
dropdownItems[i].classList.remove('is-hovered');
}
for (let i = 0; i < subMenuArrows.length; i++) {
subMenuArrows[i].classList.remove('is-active');
}
for (let i = 0; i < subMenuLinks.length; i++) {
subMenuLinks[i].classList.remove('is-open');
}
for (let i = 0; i < subNavContainers.length; i++) {
subNavContainers[i].setAttribute('style', '');
}
}
// Utility function to make window resizing more performant.
function debounce(func, wait = 100) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, args);
}, wait);
};
}
document.addEventListener('DOMContentLoaded', function() {
setAriaAttributes();
handleMobileNav();
// Set listeners for mouse and focus events
if (windowWidth > mobileBreakpointMax) {
for (let i = 0; i < subMenuLinks.length; i++) {
const linkWithSubmenu = subMenuLinks[i].parentElement;
const link = subMenuLinks[i];
linkWithSubmenu.addEventListener('focusin', () => {
link.setAttribute('aria-expanded', 'true');
link.classList.add('is-open');
});
linkWithSubmenu.addEventListener('focusout', () => {
link.setAttribute('aria-expanded', 'false');
link.classList.remove('is-open');
});
linkWithSubmenu.addEventListener('mouseenter', () => {
subMenuLinks[i].setAttribute('aria-expanded', 'true');
subMenuLinks[i].classList.add('is-open');
});
linkWithSubmenu.addEventListener('mouseleave', () => {
subMenuLinks[i].setAttribute('aria-expanded', 'false');
subMenuLinks[i].classList.remove('is-open');
});
}
}
// Arrow key controls
for (let i = 0; i < dropdownItems.length; i++) {
dropdownItems[i].addEventListener('keydown', (e) => {
if (e.keyCode === 37) {
if (i === 0) {
dropdownItems[dropdownItems.length - 1]
.querySelector('[data-dropdown-link]')
.focus();
} else {
dropdownItems[i - 1]
.querySelector('[data-dropdown-link]')
.focus();
}
}
if (e.keyCode === 39) {
if (i === dropdownItems.length - 1) {
dropdownItems[0]
.querySelector('[data-dropdown-link]')
.focus();
} else {
dropdownItems[i + 1]
.querySelector('[data-dropdown-link]')
.focus();
}
}
});
}
window.addEventListener('resize', debounce(function() {
const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
collapseSubNav();
//reset aria attributes based on vieportWidth after resize
if (viewportWidth < mobileBreakpointMax) {
for (let i = 0; i < subMenuLinks.length; i++) {
subMenuLinks[i].removeAttribute('aria-has-popup', 'false');
subMenuLinks[i].removeAttribute('aria-expanded');
}
for (let i = 0; i < subMenuToggles.length; i++) {
subMenuToggles[i].setAttribute('aria-has-popup', 'true');
subMenuToggles[i].setAttribute('aria-expanded', 'false');
}
} else {
for (let i = 0; i < subMenuLinks.length; i++) {
subMenuLinks[i].setAttribute('aria-has-popup', 'true');
subMenuLinks[i].setAttribute('aria-expanded', 'false');
}
for (let i = 0; i < subMenuToggles.length; i++) {
subMenuToggles[i].removeAttribute('aria-has-popup', 'false');
subMenuToggles[i].removeAttribute('aria-expanded', 'false');
}
}
if (viewportWidth >= mobileBreakpointMax && viewportWidth <= 1367) {
handleTabletTouch();
}
}));
});
Dropdown navigation presents a list of options to a user when activated by hovering or focusing on the parent links.
This Dropdown Navigation comes with two configuration attributes. The data-dropdown-item
attribute is used to flag top level navigation list items, and the data-dropdown-link
attribute is used to flag thier respective child main link. This assists in identification for the cooresponding Javascript keyboard functionality.
aria-expanded="false"
attribute should be added as a default state. When activated, the attribute should change to aria-expanded="true"
.aria-label="submenu name"
attribute.hidden
attribute.When interacting with dropdown navigation, screen readers should announce the following:
Tab
= Move to next focusable elementTab
= Expand/collapse dropdown←
→
= Cycle through top level links when top level links are focusedWhen navigating through dropdown menu navigation, the following tab order is expected: