How do you build a mobile-first, progressively enhanced, responsive navigation menu? Let me introduce you to Menu Object (mo.js), a system for handling menus from the ground up. At its most basic level, mo.js dynamically adds and removes classes from menu elements to allow various menu states to be styled using CSS. For those of you dying to get right to the code, you can check out the Menu Object repository on Github. For everyone else, let’s start at the beginning…
Base menu
When planning a universal menu solution, it is helpful to start with the basics. The most basic form of menu is a simple list of links positioned horizontally. If the text is centered, it can wrap to multiple lines on smaller devices and still look fine. No Javascript is required and sub-menus can be displayed using the CSS :hover state.
Granted, this is not the ideal menu in every situation, but it works acceptably in most circumstances. The biggest drawback is the lack of :hover support on certain devices, but we will address that later.
Desktop enhancements
On desktops or laptops with reasonable screen sizes, we can assume that our menu won’t be wrapping to a second line. This allows us to use CSS media queries to align our top level menu to the left. This is not required, but is often the desired menu style for non-mobile devices.
As you can see in the image above, the menu item alignment shifts to the left on larger devices.
Mobile enhancements
While it is possible to change the menu layout on mobile devices using only CSS media queries, I don’t recommend it for the following reason. Imagine a mobile menu like the one pictured below, but with 20 menu items.
Now imagine such a menu at the top of every page! On sites with such large menus, scrolling down past the menu on every new page load quickly becomes tedious.
The solution to this problem is obviously to hide the menu by default and add a menu toggle button (as can be seen in the picture above). This is typically done using Javascript. Now if the menu layout is triggered by CSS media queries, but the toggle button is triggered via Javascript, there will likely be some issues, since CSS and Javascript don’t measure viewport size in the same way. (For more details on this, Tyson Matanich’s post What size is my viewport? is a great resource.) To avoid this issue, we can use Javascript to trigger both the menu layout change and the menu toggle button. Not only does this solve the media query issue, but if a mobile device for some reason lacks Javascript support, the default horizontal menu is displayed (which doesn’t take up nearly as much screen space as a vertical menu).
In our case, mo.js automatically adds a new HTML class to the menu if the viewport is smaller than a particular breakpoint. It also adds the menu toggle button in place of the menu.
As you can see in the image above, the phone has a vertical menu with the toggle button, the tablet has a horizontal list of centered links, and the desktop/laptop combo have the left aligned list of links.
Javascript enhancements
Both of the following enhancements use Javascript to improve the handling of sub-menus.
Sub-menu indicators
Although the screen shots up to this point show no sign of the existence of any sub-menus, this particular example actually contains multiple layers of sub-menus. As (responsible) designers and developers, we should not expect our site visitors to blindly stumble upon our sub-menus, or force them to use trial and error to figure out which (if any) menu items contain sub-menus. Thankfully, mo.js automatically adds an HTML class to any menu item containing a sub-menu. This class can then be used to add a sub-menu indicator (such as an arrow) to items containing a sub-menu.
Sub-menu handling
Due to the lack of :hover support in some mobile browsers, mo.js removes the CSS hover functionality on touch devices and handles sub-menu display via Javascript. I won’t go into much detail about how this works (did I mention the code is on Github?), but basically the sub-menu indicators become click-able elements used to show/hide sub-menus.
Conclusion
…and that is one way to build a mobile-first, progressively enhanced, responsive navigation menu system. How many more buzzwords could possibly be added to that sentence?
Here’s one last screen shot with a few expanded menus.
Additional notes
- mo.js can handle multiple menus on the same page.
- mo.js uses the standard WordPress menu HTML structure. I haven’t tested it with anything else.
- I also wanted to say a special thanks to Brady Vercher of Blazer Six for his feedback and code contributions.