I still remember my excitement when I learned how to build a hover-triggered submenu with just CSS. (It was probably after reading this 2003 article from A List Apart.) At the time, it was a true CSS trick. Seriously. Wild times.
That went a little something like this:
<ul class="my-menu">
<li>
<a href="page-a.html">Page A</a>
<ul>
<li><a href="page-b.html">Page B</a></li>
<li><a href="page-c.html">Page C</a></li>
<li><a href="page-d.html">Page D</a></li>
</ul>
</li>
<!-- etc... -->
</ul>
/* Position submenus relative to parent list item */
.my-menu li {
position: relative;
}
.my-menu ul {
/* Hide my submenus by default */
display: none;
/* Position submenus, when open */
position: absolute;
left: 0;
top: 100%;
}
/* Look, Ma! No onclick handler! */
.my-menu li:hover > ul {
display: block;
}
These days, we can improve the accessibility of CSS-only menus with a newer trick! Menus can open and close when navigating them with a keyboard, thanks to :focus-within
.
/* No IE11 support */
.my-menu li:focus-within > ul {
display: block;
}
Try using both your mouse and the TAB
key to move through the demo.
But times have changed from when I first learned these tricks, and so have I. Since then, I’ve built a bunch of websites and learned a lot more about usability, accessibility, and content strategy. Now, I find hover-triggered menus lacking on all those fronts. So, a few years ago, I quit building hover-triggered submenus and switched to click-triggered submenus. (From here, I’ll just call them “hover menus” and “click menus.”)
I think you should should stop building hover menus too. I’m here to tell you why.
Hover menus are inconsistent
Take a look at this real menu from a site I built:
Simple enough, right? The arrow icons show us there are submenus for each item except “Home.” But if those submenus appear on hover, there are at least four ways the menu might work, and you’ve probably experienced all four of them.
- The top “parent” menu item links to a page and each submenu item links to another page. For the example above, “Services” would be a unique page and so would every link in the “Services” submenu.
But somewhere along the way, a second very common pattern arose.
- The parent item has
href="#"
— or even nohref
at all 😱 — and the only functional links are in the submenus. In our example, “Services” is still a link, but nothing happens when you click it.
This inconsistency — is the parent item a link or not? — leads to lots of confusion when I watch people use websites. Some people skip right past helpful top-level pages, assuming those items aren’t links. Yet others assume the top-level links are pages and try to click them.
This leads to the third and fourth not-so-great patterns you’ll encounter. My guess is that these evolved from attempts to compensate for the confusion caused by the first two setups.
- The parent item and first submenu item link to the same page. Making matters worse, the parent item and first submenu links having different link text violates a WCAG 2.1 Level AA accessibility standard.
- The parent item links to a page containing useless fluff content or only the links in the submenu. The page itself serves no real purpose for anyone visiting it.
These last two configurations waste time for people who do know the parent items are links with redundant or useless content.
Here’s a diagram showing all four possible hover menu setups.
Visitors are reasonably confused by hover menus
No matter how we implement hover menus, our visitors can reasonably wonder:
- Can I click the parent items?
- Will the parent item be a link to the same page as the first submenu link?
- Even if the parent item is a unique link, is it worth my time to view?
That leaves us with no good options. It makes it impossible to satisfy Jakob’s law of usability that “users prefer your site to work the same way as all the other sites they already know.” There is no standard implementation when it comes to hover menus, so we need to do something different to provide a consistent user experience.
What about “Split Button” menus?
Probably the least common type of menu I see uses a “split button” design where the parent item is a link and a separate drop-down icon opens and closes the menu. The Twenty Fifteen default WordPress theme uses that pattern. Because it’s so uncommon, I find that visitors often overlook the top-level page link, and research suggests that users don’t perceive a label and icon as being separately clickable.
So, what’s the better option? Enter the click-triggered submenu!
Click menus to the rescue
Instead of relying on the hover interaction or some other “creative” (and confusing) solution, let’s build menus where parent items are buttons that show and hide submenus when clicked. This instantly solves the hover menu problem because click menus work unambiguously.
- Site visitors must click the parent item to view its submenu.
- All links are contained in submenus except for top-level items that have no submenu (e.g. “Home”). We’ll deal with what happens to those top-level pages in a moment.
When you think about it, click menus are actually what we expect already in most other contexts:
- Using a touch device? Hover isn’t really a thing there.
- Using an application menu (e.g. File, Edit, etc.)? Those almost never appear on hover!
- Using anything other than a mouse? Pressing
ENTER
or activating a link with any type of switch control is more equivalent to clicking than:focus
is equivalent to:hover
.
Regardless of your device or input mode, a “click” is a more universal and solid interaction. Let’s use it to make our website menus awesome!
Switching to click menus
My gut feeling says that a lot of sites have recently switched to click menus. Join the party! As more and more sites make the change, people will again develop simple and useful expectations of “how websites work” (thereby satisfying Jakob’s law).
When you first make this change, it’s true that some visitors might still expect hover menus. They may even say they prefer them if you ask. What I can tell you from watching people use click menus, though, is that everyone figures it out quickly and adjusts.
And don’t just take my word for it! The U.S. Web Design System’s (USWDS) navigation patterns use click menus. Here’s what they have to say:
Avoid using hover to expand dropdown lists. Hover is difficult for some users and won’t work on touch screens. Dropdowns should expand on click or with keyboard navigation.
Bootstrap uses click menus, too, for these same reasons:
What it really boils down to is user intent. The purpose of a hover state is to indicate something is clickable (underlined text)… The purpose of a click is to actually do something, to take an explicit action. Opening a dropdown is an explicit action and should only happen on click.
From the same article, there’s this great nugget:
The caret in a dropdown link is the equivalent of underlining a link: it provides some affordance for what will happen when you click this element. Don’t mistake that for providing enough information to pop the dropdown on hover though.
So it’s not like we’re exploring uncharted territory here. And, the UK.gov design system has another good reminder: Maybe you don’t even need submenus at all! Their menus are just a list of links, using on-page grids of links and accordions to help visitors navigate. Heck, you won’t find submenus on CSS-Tricks either!
Click menus come with bonus benefits!
The more you work with click menus, the more benefits you discover:
- You decide whether you need a category/overview/landing page… or not! Instead of forcing content to match a menu’s structure with links that are parents of other links, your content strategy and information architecture dictates what types of pages you need and how they are labeled. If an overview of your services is helpful for your visitors, put “Services Overview” or “All Services” as the first item in your “Services” submenu.
- Submenus stay open until they are closed. Hover menus have a nasty way of disappearing when people bump their cursors or even just try to click a submenu link. This is especially the case for submenus that “fly out” rather than below the parent item. The persistence of click menus makes for a more “solid” experience so users trust the interface and don’t get frustrated.
- The persistent submenu behavior is even more crucial for megamenus. When visitors need more time to take in the submenu contents, they can’t afford to have the menu close unexpectedly.
- The JavaScript is the same for “mobile” and “desktop” menus. Whether the menu is hidden behind a hamburger or visible on mobile, the interaction is always the same. I only need to change my CSS to make a responsive click menu.
Building click menus
When I set out to build my own accessible click menu script, I found there wasn’t a single standard for how to do it. My own thoughts and code were most heavily influenced by:
- “Link + Disclosure Widget Navigation” by Adrian Roselli
- “Header demo” by US Web Design Service
- “Menu Design: Checklist of 15 UX Guidelines to Help Users” by Nielsen Norman Group
- “Example Disclosure Navigation Menu” in ARIA Authoring Practices
The key takeaways from my research on implementation:
- The element you click to show the submenu should be a
<button>
since it doesn’t link to a page. - Use
aria-expanded
(on the<button>
!) to communicate the submenu’s open and closed states. - Use
display: none
orvisibility: hidden
so that keyboard users can’t get to submenus when they are closed. aria-controls
is poop, but you might as well add it.- Do not use
role="menu"
(and the whole ARIA menu pattern) oraria-haspopup
. Those feel related but they aren’t for building navigation menus. - Close an open submenu when:
- Another submenu opens
- The user clicks outside the menu
- The user presses the
ESC
key when focus is inside an open submenu. (Not all users expect this, but I think it’s a nice touch.)
Since click menus require JavaScript, we should consider how this menu can be progressively enhanced in case JavaScript fails for any reason. Our classic hover CSS trick is still good for something after all!
I start building my click menu as a CSS-only hover menu that uses li:hover > ul
and li:focus-within > ul
to show the submenus. Then, I use JavaScript to create the <button>
elements, set the aria
attributes, and add the event handlers. This means the menu still mostly works without JavaScript and plays nicely with link-only menus built in WordPress, my CMS of choice.
You can check out the script I use below, but I’ll be the first to admit there are probably better ones. What’s important is that you test it with real users… and stop using hover menus. 😃
I’ve been experimenting using details and summary (yeah, yeah, polyfill for old browsers) for menus. The summary has the “group” name (it’s never a link) and the menu items are just an unordered list of links.
Then I added a sprinkling of JS to close any already open sibling details, enabled by a data- attribute on the parent container. That way I can reuse the component for different behaviours, such as keeping FAQs open until closed (default browser behaviour).
Just for grins I added a data-animate attribute which, well, animates the opening and closing with just CSS (see article elsewhere on CSS Tricks for that!).
As the contents of details can be styled however you like, I even have the facility to switch to using CSS columns (again via data- attribute) should the list be too tall for tablet / desktop while still keeping minimal markup (just an extra wrapping container around the list)…
Someone else raised this same idea on Twitter in response to this article, so you’re not alone in wondering! This is the approach that Github uses too.
I would say that it’s possible this approach could be used for an accessible menu, but it would need a lot of testing. In all the discussions and articles I reviewed, this pattern basically never came up, so at best it’s not well-studied or tested.
The different semantics of details/summary with a link or button also makes me nervous. It’s probably resolvable, but again, I haven’t seen anyone test it.
At the end of the day, I would still recommend my approach because it still offers a no-JS fallback behavior and has been tested and discussed in much greater detail in the accessibility community. I very much stand on the shoulders of those giants and trust their work.
Not really when you use checkbox and
:checked + .menu { display: block }
or something like that. ;)radio buttons would be better than checkboxes. since you want one menu to clsoe when the other opens
The checkbox hack is so cool and fun to use! Building custom checkboxes and radio buttons with it is possibly one of my favorite CSS tasks. Not surprisingly, CSS Tricks has covered it plenty of times. Here’s a set of examples Chris posted that includes a menu. That said, I would call out this quote from the post:
Since a
<form>
is totally different than a<nav>
element, this isn’t a pattern I considered when coming up with my approach.I think it’s worth applying Jakob’s law here specifically when it comes to accessibility. Screen reader users and people navigating with keyboards benefit even more than sighted or mouse users because the functions they rely on generally aren’t visible or otherwise perceivable. So when thinking about accessibility, it’s really important to consider semantics and what visitors are mostly likely to have encountered on other websites.
When it comes to interactions, I can’t think of two more fundamental interactive elements than
<button>
s and links, and so they are ideal for building an interface that a new visitor will immediately be able to understand as a navigation menu and interact with easily.Combining
:hover > ul
and:focus-within > ul
feels like a good enough no-JS fallback to me and supports the semantic elements (links) that every website visitor will expect to encounter in a menu.Actually, you can skip both checkboxes and radio-buttons, and use :focus-within with tabindex=”0″
Asaf – that’s what I thought until a client using safari just said the menus don’t work.
Problem is unless you set a safari preference to ALLOW tab navigation, “focus” as commonly understood/specified just does not work:
Safari focus bug
Still open since 2008…
Good news – Looks like this might get fixed soon!
(https://blogs.igalia.com/mrego/2021/06/07/focus-visible-in-webkit-may-2021/)
I’d be very interested in using this approach in a future project! Just hoping the design team can get on board with this…
Do you feel there is a proper way to bring this up without sounding like “I know best, your design is bad”?
send them this article?
It can be a strangely hard debate to engage people fully in sometimes! What’s probably most important is that you manage to get people past the “but I don’t like it” phase of usability discussions (which are not real usability discussions).
I find that when I lay out those 4 contradicting hover menu implementations, people often see that the benefits of click menus outweigh the low (and debatable) costs of not using hover menus. And if your group is big enough, you’ll probably find that people’s expectations of hover menus differ even within the group! The only way to solve the issue that I know of is to completely change the approach and use click instead.
I just brought up some of the points in this article that resonated with me, especially the fact that we can’t assume hover on desktop-sized displays anymore, and asked the design team what they thought. Every single person agreed, and we’re going with click menus from now on.
Hi Christopher,
As a UX/UI designer who is positively thrilled to see this article, I doubt that you will have a problem getting the design team on board. They may even have been waiting for someone to express the problem, and the solution, as clearly as this.
Beyond that, user testing across two versions (click vs. hover or mixed) would be interesting and the results welcomed, if you could set up an interactive test without too much fuss. Pretty much everybody sees the value in evidence-based design decisions.
what I hate the most are delayed hover menus with links on headers…
I hover, nothing happens, I click, menu appears and then “app” starts transitioning to where the header link points to… that’s simply infuriating
almost as much as “get the spam” being always checked
Did I miss the solution for the top level links?
Sorry if that wasn’t clear enough in the article.
The “solution” is to omit them (if they’re just fluff) or move them to the submenu with a clear label:
I think there is another possible hover menu setup using HTML bookmarks, and also a way to implement a click menu without JavaScript.
See here:
https://frugalwp.com/how-to-build-an-unambiguous-submenu-with-pure-css-and-html-no-javascript/
Thanks for sharing your thoughts! I see we both love to use
:focus-within
. While I love to only use CSS when necessary, part of my learning journey with submenu navigation has been learning that JavaScript is necessary in order to make an accessible menu. Among other reasons, the ARIA attributes communicate important information to screen reader users and the keyboard navigation pattern saves users time by not having toTAB
through every single link in a menu. (Providing a Skip Link also is helpful for this reason. Those are very complementary.)CSS-only is awesome as a first step in progressive enhancement, but I really think JavaScript is necessary to provide the most usable and accessible submenu navigation.
From the article: “When you think about it, click menus are actually what we expect already in most other contexts.”
[slapping my forehead] For more than a decade I’ve been dumbfounded that nobody seemed to be thinking about something so obvious. I mean, the browser itself had well-functioning, standard menus. By the time CSS enabled web dropdown menus, the behavior of dropdown menus had been extensively tested in usability labs (all sorts of variations) and almost completely standardized across Windows, Mac, NeXT, BeOS, etc: Menus, consistent with all types of button, wait to be told to open which, also consistently, can be done by mouse, keyboard, touch, voice, eye-controller, etc.
Then some web developer decided to create menus that didn’t wait for you to ask. You’d click a link at some scroll position so you’d navigate to the new page with your mouse in some random position, which would then trigger some random menu to leap out and cover what you were looking for, forcing you to find a way to get off of it so it would close without accidentally triggering another menu and starting over. And if you finally saw what you wanted on some other part of the page and moved your mouse toward it: BLAT! Some other random menu was triggered en route. Or you have menus arranged so that trying to reach one keeps triggering another nearby that then covers it (looking at you, Amazon!)
Then everyone else seems to have said, YES, I too want visitors to my page to feel as if they had just stumbled into a minefield. Instead of the extensively tested, usability-research-based standard menu behavior of clicking a menu or button when you want it, I want them to experience the thrill of not knowing what might happen and for it to happen differently depending on where they enter the page and whether they enter with a mouse or touch device.
(Tooltips are okay if they are so small that they are very unlikely to cover anything you might need. You still have to set their delay long enough to not trigger at all when passed over.)
We should be using the menu lessons that were well researched, well known, well liked, and well established back in Win95 days. We can pretend it’s a newly invented concept called “click menus” if that helps.
Thanks for your comment! I’m definitely not trying to invent a new concept. In fact, almost all of my research and work has been to avoid doing anything that hasn’t been done and tested by others.
Interestingly, I’ve never found a great term for these menus. In the accessibility world they’re know as “link + disclosure widget navigation”, but that doesn’t exactly roll off the tongue. There’s also a ton of ambiguity around the use of “dropdown”, so I didn’t want to use that.
If I missed an obvious and widely used name for click-triggered submenu navigation, I’d love to know it so I can use it and walk in the footsteps of those who have come before me!
Why not use :active for click to open?
:active
only applies when a mouse click goes down and stops applying (most of the time) when the mouse click goes back up. Therefore, using:active
would only keep a menu open when holding the mouse button down.Here’s a demo to show you when
:active
applies: https://codepen.io/mrwweb/pen/eYgYOVY?editors=1100Easy… just click, drag off, then unclick. Element stays active! Easy peasy… just teach your visitors this new method of menu activation.
/s, in case it wasn’t obvious…
You might find my jsMenus library a useful tool. It has a number of nice-features, including full keyboard navigation – try the demo here. I have not attempted to make it gracefully degrade if there is no JavaScript (because I mainly use it for commands, not links). I haven’t yet done much to add attributes and classes for accessibility, but I fixed the element hierarchy today to make it more suitable. Feedback welcome.
Thanks for sharing! Definitely checkout the ways that ARIA can make a menu more accessible by communicating the open/closed state of submenus! Sounds like it would be a good addition to your script!
I’ve implemented most of your main recommendations. The main exception is aria-controls, which you don’t seem to think it is terribly useful (but I could add it if requested). Each menu-item is now a
<button>
; the menu-item node wraps both the button and any submenus; and aria-expanded is set as appropriate. Most of the aria-recommended keybindings work. (The main known missing bindings are Space and Tab, but I haven’t studied the recommendations very carefully.) Please post an Issue if you want to use the library and have accessibility (or other) concerns.Nice article! I have got two questions:
1: Why omit the aria-menu pattern? W3C is using them in their examples? Whats it good for if not navigations?
2: You say “use buttons for top-level entries”. In your codepen you are using Services Why so?
It’s true that there are even multiple demonstration examples for navigation menus (in the role=menu sense) provided there. However, the pattern has been contentious.
Some folks think they should almost never be used. In particular, Adrian Roselli has strong feelings about this and raised issues within WCAG requesting their removal. You can read about his views here: https://adrianroselli.com/2017/10/dont-use-aria-menu-roles-for-site-nav.html
As often occurs in accessibility, single blog posts can end up being very influential, especially if written with a certain tone. That article is an example — if you hear someone say that the menu patterns should never be used, I think there’s a pretty good chance they learned it there or picked it from someone who did, etc. It’s certainly where I first learned it.
The conclusions there haven’t been accepted by everyone and attempts to remove the pattern or add a specific disclaimer have been unsuccessful (for years now). Some people using assistive tech prefer menu behavior for navigation and some don’t. Some people not using assistive tech prefer menu behavior and some don’t (they’re quite different experiences regardless).
In any case, while some folks do prefer menus, no one is likely to be surprised by not using them. Given menus are more complicated and there are more ways things can go wrong, even if one doesn’t agree with all of Roselli’s conclusions, the core advice remains sound.
@bathos’ reply is great!
I will admit to reading and finding Adrian Roselli’s arguments on this subject compelling and influential on my own views.
I would say my own technique really boils down to the conclusion of their response:
It seems very clear that we can make a solid usable pattern without role=”menu”, and the added complexity does not seem worth any advantages we might gain (in my view).
Regarding your second question about buttons, “use buttons for top-level entries” means that you should use the button element rather than the link element for a clickable item that shows a submenu. “Services” is the button text (accessible name) of the first button in the navigation menu. Hope that clears things up.
I like the advice here. Menus are such an important part of navigating the internet, they should be an area of usability focus.
I think one of the reasons bad menus have proliferated so much is because out-of-the-box WordPress forces all menu items to be links. This has been one of my biggest gripes with WordPress for many years now.
Great article, I’ve come to the same conclusions lately.
The one thing I haven’t yet found a clear solution for is how to handle breadcrumbs when the first navigaiton level doesn’t exist with a dedicated URL.
I agree with Nielsen that every but the last item in a breadcrumb navigation must have an URL (https://www.nngroup.com/articles/breadcrumbs/ Section 6).
However, just leaving that first category out of the breadcrumb leads to a poor indication of the current position within the logical structure of the site.
Then there’s the option of having that first-level menu item as an URL that does only redirect to the first sub page. Yet that produces for a very unpredictable behaviour which I also can’t imagine to be a good experience that is easily understood.
My conclusion for now is
– leave out breadcrumbs for sites with hierarchies <3 levels (and inidicate position clearly in the menu itself)
– open a popover when clicking a first-level item inside a breadcrumb which reveals sub items. While not being your regular breadcrumb link I think it’s most consistent with expectations (one can click on breadcrumb items), best indicates the current position and repeats the pattern known from the main menu itself (click in first level expands list of second level items).
I’m very curious how other people approach breacrumbs in the context of click-menus.
The answer is less to omit fluff pages and add value to those pages first. A “products” page doesn’t have to be fluff – it can speak to a user who doesn’t yet know which product segment they need and can help them find it.
So I wouldn’t be so dismissive of fluff pages. If you have a fast-loading site, the time to click to a page and then choose a child can be teh same or faster as a menu expansion, comprehension of the menu, and a second click within the menu itself.
@Nathan, I totally agree! That’s definitely one of the options that I propose in the article. What I think it important is that people explicitly make a decision about whether they need landing/index pages or not.
I would be a little careful with this argument for a couple reasons:
1) A page refresh is almost certainly slower, even on the fastest loading sites, and we always want to account for bad networks and slow internet connections.
2) Loading a page to discover that it’s not what you want feels like a much more expensive “interaction cost” than opening a submenu.
I’ve been using jQuery for a while, but lately I’ve been trying to learn how to code with plain vanilla JS just to do this kind of thing.
It’s not perfect, but here’s my go at a clicky dropdown menu:
I chose not to turn the menu toggle anchors into buttons as my wife, who uses various screen readers and is an accessibility consultant for her workplace, said that having role=”button” and providing proper ARIA attributes as necessary is sufficient.
I would greatly appreciate any constructive advice as to what I can do to improve the function of the menu.
Thanks for your time and attention!
It sounds like your wife knows what she’s talking about so I don’t want to second guess her.
I chose to convert to a button because of the “First Rule of ARIA”, written straight into the specification:
Rather than having to worry about a potential edge-case or button behavior I might not know about, I’d rather just use a button.
Thank you @Mark Root-Wiley for writing this article! And to @SiVal for your very apt description of the problem. I’m working on implementing this in my WordPress client themes right now.
My current client wants a third level of submenu, though. Does anyone reading this have thoughts or recommendations on how to implement this?
Making a submenu item a button might be fine, but clicking it closes the submenu list, and the JavaScript is beyond me at this point. But maybe there’s a better solution, or other things I should know before I go down a rabbit hole with this.
Theme developers who want to incorporate this into a theme for WP’s library will have to come up with a general solution. I’m excited to see what’s possible!
@clgolden – I’m glad you like it!
Multiple levels of submenus is something I’ve considered, though you’re right that the script currently doesn’t support it. It’s definitely a case where I don’t like the information architecture of nested submenus so I don’t support it for now. I also like that by not support it, it’s easy to make megamenus with this script where a single submenu can contain multiple visible levels of nested menu items.
It’s an interesting trade-off, and I think you’re right that a WordPress theme would want to support nested submenus by default.
Great article. I’m trying to implement this on a new site I’m working on and am running into the following console error:
Uncaught DOMException: Failed to execute ‘closest’ on ‘Element’: ‘#’ is not a valid selector.
Which is being called from the closeOpenMenu function.
On desktop this happens everytime I click a menu with a dropdown. It still appears to work, but I’m not sure why that error keeps popping up on mine when it doesn’t in your CodePen.
Any thoughts?
Hey Todd,
I believe the issue here is that the element wrapping your menu doesn’t have an ID set. This is an implicit requirement that I hope to either remove or address with the script in the future (here’s a Github issue).