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 class="dropdown">
<a href="#">About</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 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;
&:hover .dropdown__toggle {
@media (hover: hover) {
pointer-events: none;
}
}
&:hover .dropdown__menu {
@media (hover: hover) {
opacity: 1;
visibility: visible;
transform: translate(-50%, 0);
transition: 300ms all 120ms ease-out;
>* {
opacity: 1;
transform: translate(0, 0);
transition: 150ms all 75ms ease-out;
}
}
}
.dropdown__menu.-active {
opacity: 1;
}
}
.dropdown__menu {
min-width: 100%;
padding: 0;
position: absolute;
top: calc(100% - 0.25rem);
left: 50%;
transform: translate(-50%, -5px);
transform-origin: top center;
opacity: 0.3;
transition: 50ms all 0ms ease-out;
visibility: hidden;
>* {
opacity: 0;
transition: 50ms all 0ms ease-out;
}
}
.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(dropdownElements) {
dropdownElements.forEach((dropdown) => {
const toggle = dropdown.querySelector('.dropdown__toggle');
toggle.addEventListener("click", this.toggle.bind(this));
dropdown.addEventListener("mouseenter", this.mouseEnterHandler.bind(this));
dropdown.addEventListener("mouseleave", this.mouseLeaveHandler.bind(this));
});
}
mouseEnterHandler(e) {
const dropdown = e.target.closest('.dropdown');
dropdown.classList.add('-active');
}
mouseLeaveHandler(e) {
const dropdown = e.target.closest('.dropdown');
dropdown.classList.remove('-active');
}
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
Section titled “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
Section titled “Keyboard Navigation”With this approach, keyboard users now have the ability to fully access all of the menu items via tabbing.
Touch devices
Section titled “Touch devices”Users on touch capable devices can click top level menu links and toggle the visibility of submenus.
Accessiblity Considerations
Section titled “Accessiblity Considerations”- The submenu toggle button should have the following attributes:
aria-expandedwith a boolean value to indicate the visibility state for non-sighted users.aria-controlswith the id of the submenu to indicate what element the button is controlling.aria-labelwith 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.