Menu Button
A button that expands to reveal a menu within a flyout.
Last updated
A button that expands to reveal a menu within a flyout.
Last updated
The menu button pattern is a JavaScript widget. It is a button that expands to reveal a menu in a flyout.
A menu is appropriate when requiring a partial page re-render without using a form or full page reload. For example: filtering and sorting of search results.
A menu is not appropriate for a full page reload. For that, please use links instead (see the Fake Menu pattern). 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 your menu must contain a mix of JavaScript behaviour and link behaviour, please use a regular list of buttons and links. Do no mix menu items with links.
TIP: Do not call a menu button a "dropdown"! The term "dropdown" is ambiguous and could be confused with a listbox, combobox select or any other kind of overlay that "drops down". If you must, call it a dropdown menu.
Experience the pattern in action on our companion eBay MIND Patterns examples website.
Examine the required markup structure on our Bones project on GitHub.
View a fully styled example in our eBay Skin CSS framework.
widget: the pattern as a *whole*, comprising the parts listed below
button: expands or collapses the overlay
collapsed/expanded: state of overlay
overlay: contains menu
menu: a menu that contains commands
command: individual menu item, menu item checkbox or menu item radio commands
See menu best practices.
Care is needed when labelling a menu button!
If attempting to mimic the behaviour of an HTML select inside of a form, please use the Listbox Button.
A menu button's accessible label must at all times reflect its function. By default, this label is provided by the button's inner text (i.e. buttons are not intended to work with <label>
elements).
If a menu button's inner text is intended to also convey some state (i.e. single select) then please consider the following options.
The button's inner text can be written as a key/value pair, where key denotes purpose and value represents the current selection.
<button>Colour: blue<button>
We can use aria-labelledby
to stitch together an external text element with the internal value.
<span id="el1">Colour</span><button aria-labelledby="el1 el2"><span id="el2">blue</span></button>
This section provides interaction design for keyboard, screen reader & pointing devices.
Please also see related menu pattern for best practices of nested menu.
The button must be keyboard focusable.
SPACEBAR
or ENTER
key on button must expand the menu.
When menu is expanded, keyboard focus must go to the first item in the menu.
UP-ARROW
and DOWN-ARROW
keys must navigate keyboard focus through commands via a roving tabindex.
If focus is on a command, ENTER
or SPACEBAR
keys must activate that command.
ESC
key must collapse menu and return focus to button.
Activating any menu item should collapse menu (typically after a very short delay/transition).
TAB
key must move keyboard focus off widget, and onto next interactive element in the page.
When widget loses focus, menu should collapse.
Button label must be announced (e.g. 'Options').
Button state must be announced (e.g. expanded or collapsed).
Clicking any menu item should collapse menu (typically after a very short delay/transition).
While 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.
For our developer guide we will create a menu that filters search results. The menu will be opened via a button.
Button
First we add our button:
Ungrouped Menu
The simplest kind of menu contains just regular menu items. You can think of the menuitem
role as similar to a button.
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! To clear up confusion over this attribute, ARIA 1.1 introduced new values such as menu
, dialog
and listbox
.
Grouped Menu
Menus can also contain groups of different types of menu item. For example: a group of menu items, a group of menu item radios, and a group of menu item checkboxes.
Each group must be separated with a separator tag (implicit role="separator").
NOTE: We have encountered issues when trying to use role="separator"
on a list tag. It is for this reason that use div-based markup, rather than list-base markup, in the examples.
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 have we specified it again explicitly? When testing in various screen readers, more consistent behaviour was noted with the role specified explicitly.
Checkpoint
At this point we have our server-side markup with ARIA states.
You could also choose to render the overlay markup on the client, or even lazy-load it when the button is clicked.
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.
We can display the menu utilising the ARIA state of the button and the general sibling selector:
Or leverage a class if necessary (i.e. if the button is not an adjacent sibling):
The overlay will only be visible when the aria-expanded state is true (or if the expanded class is present, if you choose). It is the job of JavaScript to toggle this state.
The goals of the behaviour layer are to:
state management: toggle the aria-expanded
and/or .menu-button--expanded
state on click
focus management: implement roving-tabindex keyboard navigation on menu items
Toggling State
CSS alone cannot change the value of an HTML attribute; JavaScript is required. Fortunately, the makeup-expander module can handle this behaviour in just a few lines of code.
When the menu opens, focus should move to the first menu item. This is handled by the focusManagement
option.
Keyboard Navigation
Menu Button now begins to deviate from the base flyout pattern. A base 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. However, a menu has a very specific kind of content - menu items - and these menu items are navigated with the UP-ARROW
and DOWN-ARROW
keys.
We have another JavaScript module that helps, the makeup-roving-tabindex module.
View the roving tabindex technique for further details.
We have some JavaScript modules that may assist you with creation of an accessible menu button widget:
makeup-expander - Useful for implementing a button that opens a non-modal overlay
makeup-roving-tabindex - Useful for implementing the arrow key behaviour to change menu items
This section gives an overview of our use of ARIA, within the specific context of the menu button pattern.
Informs assistive technology that this is a menu containing menuitems, menuitemradios or menuitemcheckboxes.
Informs assistive technology that the divs around groups of menu items are for presentation purposes only and should not be added to accessibility tree.
Informs assistive technology that this menu command has button behaviour.
Informs assistive technology that this menu command has radio button behaviour.
Informs assistive technology that this menu command has checkbox behaviour.
Informs assistive technology that the button controls a popup menu
Inform assistive technology of which menu this button controls.
Informs assistive technology whether the popup menu is expanded or not. And yes, this state goes on the button, not the menu.
Informs assistive technology whether the menuitemradio or menuitemcheckbox is checked or not. Notice we do not use aria-selected.