eBay

Menu

Screenshot of privacy settings menu

Introduction

The menu pattern is a click-activated flyout. The menu overlay may contain menuitem, menuitemradio or menuitemcheckbox commands.

A menu is commonly used when requiring a partial page refresh without using a form or full page reload. For example: filtering and sorting of search-results.

If you desire links instead of menuitems, please use the fake menu pattern instead. The distinction between menu items and links is important! A menu item is a command that executes JavaScript, whereas a link is a command that navigates to a url.

If the menu must contain a mix of menuitems and links, you should use the fake menu pattern, replacing menuitems with buttons. It is more accessible to put buttons into a fake menu than putting links into a menu.

TIP: Do not call a menu a "dropdown"! The term "dropdown" is ambiguous and could be confused with a listbox, combobox or any other kind of overlay that "drops down".


Working Examples

You can take a look at the menu pattern in action on our examples site.

You can get a quick idea of the required markup structure by viewing our bones project.


Terminology

menu
the pattern as a *whole*, comprising of a button, overlay and commands.
button
expands or collapses the overlay
collapsed/expanded
hidden/visible state of overlay
overlay
contains commands
command
menuitem, menuitemcheckbox or menuitemradio

Best Practices

Remember, a menu is not a form control. It's purpose is not to store or submit data via form submission. A menu, like a button, is a mechanism to execute JavaScript, and therefore is 100% dependent on JavaScript.

If you are concerned that the functionality of the menu must be available in a non-JavaScript state, then perhaps a menu is not the best choice.

Adding, modifying or deleting records (e.g. CRUD) could be considered critical functionality. Filtering and sorting search results could be considered non-critical.

Selecting a command should not fully reload the page. Commands should perform a JavaScript action on the client (e.g. AJAX request then partial page re-render).


Interaction Design

This section provides interaction design for keyboard, screen reader & pointing devices.

Keyboard

If focus is on button, ENTER & SPACEBAR keys must expand menu.

When menu expands, focus must move to first command.

UP and DOWN arrow keys must navigate keyboard focus through commands.

If focus is on a command, ENTER or SPACEBAR key must activate that command.

If focus is on a command, TAB key must collapse menu and focus moves to next page control.

If focus is on a command, ESC key must collapse menu and return focus to button.

Screen Reader

Button label must be announced (e.g. 'Options').

Button state must be announced (e.g. expanded or collapsed).

Commands must be announced as "menuitem", "menuitemradio" or "menuitemcheckbox" role.

Checkbox and radio command state must be announced as checked or unchecked.

Pointer

Button click must toggle expanded state.

Command click must change state to collapsed.


Developer Guide

HTML does provide a menu tag. However, at the time of writing, this tag is only supported in Firefox. Needless to say, we shan't be going down that route then.

It is technically feasible for a menu to fallback to a set of form controls (i.e. button, checkbox and radio) while in a non-JavaScript state. However, as mentioned, this defeats the true purpose of a menu (which is to run JavaScript). Therefore our menu will be dependent on JavaScript.

Content (HTML)

For our developer guide we will create a menu that filters search results.

Button

First we add our button:

<div class="menu">
    <button class="menu__button" type="button" disabled>Search Options</button>
    <!-- overlay will go here -->
</div>

We set the button to disabled state by default. We will enable it later with JavaScript.

Why do we disable it? Well, It is important to remember that a button of type="button" will do nothing until JavaScript is available. By explicitly disabling the button in the meantime, we at least set an expectation that it is not yet ready for use.

TIP: Another option is to not render the button on the server-side at all, but to wait and render it on the client.

Ungrouped Menu

The simplest kind of menu contains just menuitems.

You can think of the menuitem role as similar to a button.

<div class="menu">
    <button aria-controls="menu1" aria-expanded="false" aria-haspopup="true" class="menu__button" type="button">Search Options</button>
    <div class="menu__overlay" id="menu1" role="menu">
        <div role="menuitem">Show Less Results</div>
        <div role="menuitem">Show More Results</div>
    </div>
</div>

Notice the addition of ARIA on the button. If you ever wondered what aria-haspop is for, well now is the time to use it!

Grouped Menu

Menus can also contain groups. For example: a group of menuitems, a group of menuitemradios, and a group of menuitemcheckboxes.

Each group must be separated with a separator tag (implicit role="separator").

Now, I have encountered issues when trying to use role="separator" on a list tag. It is for this reason that we switch from list-based markup, to div-based markup, in the example below.

<div class="menu">
    <button aria-controls="menu1" aria-expanded="false" aria-haspopup="true" class="menu__button" type="button">Search Options</button>
    <div class="menu__overlay" id="menu1" role="menu">
        <div role="presentation">
            <div role="menuitem">More Results</div>
            <div role="menuitem">Less Results</div>
        </div>
        <hr />
        <div role="presentation">
            <div aria-checked="true" role="menuitemradio">Sort by Name</div>
            <div aria-checked="false" role="menuitemradio">Sort by Price</div>
            <div aria-checked="false" role="menuitemradio">Sort by Date</div>
        </div>
        <hr />
        <div role="presentation">
            <div aria-checked="true" role="menuitemcheckbox">Show Buy It Now</div>
            <div aria-checked="true" role="menuitemcheckbox">Show Auction</div>
        </div>
    </div>
</div>

We use ARIA to set the role and state of each command.

NOTE: A div tag already has an implicit role of presentation, so why specify it again explicitly? When testing in various screen readers, I noticed more consistent behaviour with the role specified explicitly. I will monitor this situation periodically; you have my solemn vow.

Checkpoint

At this point we have our server-side markup with ARIA states.

The button is visible, but disabled. The overlay is hidden.

You could also choose to render the overlay markup on the client, or even lazy-load it when the button is clicked.

Presentation (CSS)

The goal of our presentation layer is to add the hooks for hiding and showing the overlay.

The guidance here is actually identical to the flyout pattern, but we repeat it again here in the context of a menu.

The overlay is always hidden by default. The overlay must be absolute or fixed position with z-index.

.menu__overlay[role="menu"] {
    display: none;
    position: absolute;
    z-index: 1;
}

We can display the menu utilising the ARIA state of the button and the general sibling selector:

.menu__button[aria-expanded="true"] ~ .menu__overlay {
    display: block;
}

The overlay will only be visible when the aria-expanded state is true. It is the job of JavaScript to toggle this state.

Behaviour (JS)

The goals of our behaviour layer are to:

  1. toggle the aria-expanded state of button on click
  2. implement roving tabindex keyboard navigation on menu items

Toggling State

This is simple, and again follows the same guidance as the flyout pattern.

$('.menu__button').on('click', function(e) {
  var isExpanded = $(this).attr('aria-expanded') === 'true');

  $(this).attr('aria-expanded', isExpanded ? 'false' : 'true');
});

Keyboard Navigation

Here is where we deviate from the flyout pattern.

A generic flyout pattern makes no assumption about the contents of the overlay. We expect to use TAB key to move focus through any focusable children of the overlay.

A menu has a very specific kind of content - menu items - and these menu items are navigated with the up and down ARROW keys.

View the roving tabindex technique for further details.


Useful Plugins

We have some experimental jQuery plugins that may assist you with creation of an accessible menu widget:

  • jquery-click-flyout - Useful for implementing a button that opens a non-modal overlay
  • jquery-roving-tabindex - Useful for implementing the arrow key behaviour to change menu items
  • jquery-menu - Experimental plugin that recreates the accessibility requirements for a basic ARIA menu (not considered production-ready).

ARIA Reference

This section gives an overview of our use of ARIA, within the specific context of the menu pattern.

role=menu

Informs assistive technology that this is a menu containing menuitems, menuitemradios or menuitemcheckboxes.

role=presentation

Informs assistive technology that the divs around groups of menu items are for presentation purposes only and should not be added to accessibility tree.

role=menuitem

Informs assistive technology that this menu command has button behaviour.

role=menuitemradio

Informs assistive technology that this menu command has radio button behaviour.

role=menuitemcheckbox

Informs assistive technology that this menu command has checkbox behaviour.

aria-haspopup

Informs assistive technology that the button controls a menu. The name of this property is slightly misleading in that it implies it can be used for any kind of popup. This is not the case! This property is only for use with Popup Menus (not dialogs, tooltips, or bubble help, for example).

aria-controls

Inform assistive technology of which menu this button controls.

aria-expanded

Informs assistive technology whether the popup menu is expanded or not. And yes, this state goes on the button, not the menu.

aria-checked

Informs assistive technology whether the menuitemradio or menuitemcheckbox is checked or not. Notice we do not use aria-selected.

results matching ""

    No results matching ""