Usage
<script src="/src/js/dropdown-navigation-init.js"></script>
<nav class="dropdown-nav theme container navigation" aria-label="Main Navigation">
<ul class="l-cluster" role="list">
<li><a href="#">About</a></li>
<li class="dropdown">
<a href="#" aria-current="true" target="_blank">Products</a>
<button type="button" class="dropdown__toggle" aria-expanded="false" aria-label="Toggle Products Submenu" aria-controls="submenu-1">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
<ul class="dropdown__menu" id="submenu-1">
<li><a href="#">Item 1</a></li>
<li><a href="#">Item 2</a></li>
<li><a href="#">Item 3</a></li>
<li><a href="#">Item 4</a></li>
</ul>
</li>
<li><a href="#">Order</a></li>
</ul>
</nav>
.dropdown-nav > ul {
> li.dropdown > a {
@media screen and (hover: hover) {
padding-right: 30px;
}
}
}
.dropdown {
position: relative;
transition: 0ms all 120ms ease-out;
&:hover .dropdown__toggle {
@media (hover: hover) {
pointer-events: none;
}
}
&:hover .dropdown__menu {
@media (hover: hover) {
opacity: 1;
transform: rotateX(0) translateX(-50%);
visibility: visible;
}
}
.dropdown__menu.-active {
opacity: 1;
transform: rotateX(0) translateX(-50%);
visibility: visible;
}
}
.dropdown__menu {
min-width: 100%;
padding: 0;
position: absolute;
top: calc(100% - 0.25rem);
left: 50%;
transform: translateX(-50%);
transform: rotateX(-90deg) translateX(-50%);
transform-origin: top center;
opacity: 0.3;
transition: 0ms all 120ms ease-out;
visibility: hidden;
}
.dropdown__toggle {
background-color: transparent;
border: none;
font-family: inherit;
line-height: 1;
padding: 0;
svg {
display: block;
width: 24px;
height: auto;
}
@media screen and (hover: hover) {
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
}
}
export default class DropdownNavigation {
constructor(dropdownToggleElements) {
dropdownToggleElements.forEach((dte) => {
dte.addEventListener("click", this.toggle.bind(this));
});
}
toggle(e) {
const btn = e.target;
const parent = btn.parentElement;
const expanded = btn.getAttribute("aria-expanded") === "true";
const dropdownMenu = parent.querySelector(".dropdown__menu");
btn.setAttribute("aria-expanded", !expanded);
dropdownMenu.classList.toggle("-active");
}
}
Hover Capable Devices
By utilizing the css media query @media screen and (hover: hover)
we can create separate navigation experiences for users on hover capable devices. Pointer events are disabled on hover and padding is used on the link to “hide” the sub-menu toggle button from pointer devices.
Note, the hover transition has a slight delay (120ms) to reduce incidences of “drive by” hovering.
Keyboard Navigation
With this approach, keyboard users now have the ability to fully access all of the menu items via tabbing.
Touch devices
Users on touch capable devices can click top level menu links and toggle the visibility of submenus.
Accessiblity Considerations
- The submenu toggle button should have the following attributes:
aria-expanded
with a boolean value to indicate the visibility state for non-sighted users.aria-controls
with the id of the submenu to indicate what element the button is controlling.aria-label
with a description of the buttons purpose.
- Top-level menu items have a visual indicator for when they contain a submenu.
- Top-level menu items have a visual indicator for when a menu item is selected via hover or focus.