So, let's say you're working within a framework, like WordPress, and you don't have great access to the generated code. Here's how you might work around that to implement some nifty dropdown menus.
The HTML
Here’s the key takeaway(s):
- Make sure your container element has the
data-dropdown=".classname-of-dropdown"
- Ideally, the element in you define in that attribute will have a sub menu in the form of a
<ul>
<header data-dropdown=".menu-item-has-children">
<ul class="dropdown-menu">
<li class="menu-item">
<a href="/">Home</a>
</li>
<li class="menu-item menu-item-has-children">
<a href="/classes">Classes</a>
<ul class="sub-menu">
<li class="menu-item">
<a href="/class-type-i/">Class Type I</a>
</li>
<li class="menu-item">
<a href="/class-type-ii/">Class Type II</a>
</li>
<li class="menu-item">
<a href="/class-type-iii/">Class Type III</a>
</li>
</ul>
</li>
<li class="menu-item">
<a href="/pricing/">Pricing</a>
</li>
<li class="menu-item">
<a href="/policies-rules/">Policies and Rules</a>
</li>
</ul>
</header>
The CSS
We need to make sure the .menu-item
has a relative position and also show the .sub-menu
when aria-active
is set to true
.
.menu-item {
position: relative;
}
.sub-menu {
display: none;
position: absolute;
}
.menu-item-has-children[aria-expanded=true] .sub-menu {
display: block;
}
The JavaScript
Dropdowns
First we need to check for our data-dropdown
element(s) to see if they exist.
If they don’t, we return early and go about the rest of our day.
Otherwise, we get each of our data-dropdown
elements and pass them to our Dropdown
function:
function Dropdowns() {
const dropdowns = document.querySelectorAll("[data-dropdown]");
if (!dropdowns) return;
dropdowns.forEach(Dropdown);
}
The Dropdown Function
This function gets every HTML element we expect to have a dropdown and passes them to our DropdownElement
function where we can finally get to work!
function Dropdown(wrapper) {
const elSelector = wrapper.dataset.dropdown;
const elements = wrapper.querySelectorAll(elSelector);
elements.forEach(DropdownElement);
}
The DropdownElement Function
This function listens for when the parent
of the submenu is focus
ed, hover
ed, touch
ed, or click
ed and reacts accordingly.
function DropdownElement(el) {
const subMenu = el.querySelector("ul");
if (!subMenu) return;
const subMenuId = `submenu-${el.id}` || `submenu-${Math.random()}`;
subMenu.id = subMenuId;
el.setAttribute("aria-controls", subMenuId);
let open = false;
const link = el.querySelector("a");
const onLinkClick = (e) => e.preventDefault();
const closeSubMenu = () => updateOpen(false);
const openSubMenu = () => updateOpen(true);
function onLinkTouch(e) {
e.preventDefault();
updateOpen(!open);
}
function onElMouseenter() {
openSubMenu();
el.classList.add("has-hover");
}
function onElMouseleave() {
closeSubMenu();
el.classList.remove("has-hover");
}
function updateOpen(state) {
open = state;
el.setAttribute("aria-expanded", open);
}
el.addEventListener("mouseenter", onElMouseenter);
el.addEventListener("mouseleave", onElMouseleave);
link.addEventListener("click", onLinkClick);
link.addEventListener("touchend", onLinkTouch);
el.addEventListener("focusin", () => {
openSubMenu();
el.classList.add("has-focus");
});
el.addEventListener("focusout", (e) => {
if (el.contains(e.relatedTarget)) return;
closeSubMenu();
el.classList.remove("has-focus");
});
}
Conclusion
That’s all folks! In the next article, we’ll focus on making these dropdown menus animate!