What's on the menu?

What is on your mind if I just write the word "menu" without giving further context? I bet that either you are not sure what exactly it is referring to (because it is an umbrella term), or you come to a definition of the word that differs from the next person. Note: The following is a slightly edited excerpt of chapter 4 of "Accessible Vue".

Unfortunately, that is one of the main issues when dealing with "menus" in accessibility. "Menu" alone is not enough, and in order to be more precise and to not confuse different things, especially in the web app context, we have to establish two much more pronounced terms. After this distinction is made, I will present ways to build the one or the other in Vue.

Navigation menus

The first question one has to ask is, "What purpose does the component have?". Is it a list of items that - upon interaction - leads to another page, route or file, respectively? See how I described the word "link"? Do these items lead to web app states that can be reached under their own URLs? If so, you are likely building a navigation menu.

In the wild, navigation menus are often found in mobile or narrow screen contexts where a part of the navigation collapses into "a menu" either completely, or only part of it does.

Maybe you read about the concept of a disclosure widget in an earlier blogpost of mine, and this is actually the best choice for a navigation menu (Quick aside: I learned this with a detour, hat tip Heydon Pickering. Simple solutions are most of the time the best ones).

Let's imagine you have an e-commerce app which has an account button. Your designer's concept is that, once the button is clicked, a container with two links appears. The first item being "Past Orders" (and it leads to a route where your past orders are listed), the second one being "My Address", which leads to a route (or page) where you can change your postal address(es) for delivery. Probably you would build such a component like this:

<template>
    <div>
        <button
            @click="toggleOpen"
            :aria-expanded="!open.toString()">My Account</button>
        <div :hidden="open">
            <ul>
                <li><a href="#">Past Orders</a></li>
                <li><a href="#">My Address</a></li>
            </ul>
        </div>
    </div>
</template>
<script>
export default {
    data() {
        return {
            open: false
        }
    },
    methods: {
        toggleOpen() {
            this.open = !this.open;
        }
    }
}
</script>

You may recognize the pillars of the disclosure widget. Since it is a central widget pattern for accessibility, let's reiterate its important parts here:

  • I chose a button as the triggering element. Thus, the @click event listener also listens to SPACE and ENTER key events. This comes for free because when using the <button /> element.
  • I made sure the disclosed content comes right after the button in the DOM.
  • The disclosed content has the hidden attribute on its container (therefore is hidden to all users). The value of hidden attribute is made reactive. Initially, it is set to the opposite of open (!open). Also, the button indicates that interaction with it influences the perceptibility of another element. Unlike the reactive hidden, I made sure the boolean that is the value of aria-expanded is cast into a string. Otherwise, Vue wouldn't render the attribute altogether when it's falsey. Alas, in this case, users of assistive technology would not notice that this button has a disclosure-toggling function. So the aria-expanded attribute has to stay present under all circumstances.
  • As you can see from the slim <script> block, no focus management.

Now that navigation menus should be the most common form of menus, it totally makes sense to put the "disclosure widget" parts into a component and let other components use instances of it.

Find working "navigation "CodeSandBoxes for Vue 2 here and Vue 3 here.

Action Menus

Most of the menus you are about to build in your apps will come down to navigation menus (and, therefore, to disclosure widgets).

Some menus of your web app are meant to behave like the menus of your machine's operating system. When you click a menu item, something should happen (for example, your printer should start to do its job). In web development (and semantics), we got the general distinction of interactive elements that "lead to places" (links) and ones that "change state", or, sloppily put, "do something" (buttons). So, if a menu exclusively consists of actionable items, and you are aware that you a) will need to implement a special keyboard pattern and b) will send most screen readers into a special "app mode" – only then use the action menu concept.

However, even then you have to buckle up for a much more complex pattern. If I would describe it in detail here, making the subsection about action menus way longer than the one with the easy and rather short pattern of navigation menu – and then I would be doing accessibility a disservice. A complex pattern leads to more explanations and code examples, which leads to more text. This itself could lead to "perceived importance" of this pattern, which I'd like to avoid (because a disclosure widget is a right choice for 99% of your menu needs).

I try to solve this dilemma by pointing you to two resources:

To conclude: When you look into the linked Vue implementation, you'll learn that an action menu (according to the Authoring Pattern) is a complex beast, and most of the time, not worth the hassle.

Oh, and one thing about the ARIA roles of menu and menuitem you'll find in the implementation: These roles are intended for action menus only and should not be used for navigation. "Menu item" is here synonymous with "action menu item". Like I said, the situation around the term menu is broken beyond repair.

The takeaway

Finally, if you only remember two things of this section, make it these:

  1. If in doubt, use the disclosure widget pattern (as described in chapter 2, and above).
  2. Use role="menu" and role="menuitem" sparingly and not for navigation purposes.

Subscribe

If you want to stay in informed about updates on marcus.io and at my other projects, enter your email address below or or subscribe to the RSS feed