jQuery Menu: Dropdown, iPod Drilldown, and Flyout styles with ARIA Support and ThemeRoller Ready

April 2020 note: Hi! Just a quick note to say that this post is pretty old, and might contain outdated advice or links. We're keeping it online, but recommend that you check newer posts to see if there's a better approach.

We got lots of fantastic feedback on our earlier iPod-style menu, and decided to upgrade it for jQuery 1.3. In the process of refining the script, we morphed it into more of a menu system, which can be used to create a simple dropdown menu for a single list of options, a flyout menu for a smaller hierarchical list of options where child menus are displayed next to the parent menu on mouseover, and two variations on the iPod style, one with a back button and another with a linked breadcrumb to let users easily traverse back up the hierarchy.

Our original iPod-style menu provides easy navigation of complex nested structures with any number of levels. The entire menu sits within a fixed-size area, and when a node is selected, by default a “Back” link appears below the menu to allow navigation to previous (parent) menus, or set options to show breadcrumb links above the menu options to both deliver feedback and allow quick access to nodes higher up in the hierarchy. A lateral iPod-style slide transition reinforces the notion that you’re moving forward into more granular data, or backward into higher-level data. This menu is specifically useful in traversing deeply nested hierarchies, particularly those more than three levels deep. It also provides a more usable alternative to multi-level fly-out menus, which can present challenges to those with limited manual dexterity.

Much of the functionality that controls the iPod style menu, like positioning and option selection, is shared by dropdown and flyout menus which are better suited for smaller data sets, so we extended the script to include support for these additional menu types.

We also built in the ability to customize a menu’s appearance, including active and hover states, by passing in classes as options. In the examples below, we plugged in jQuery UI CSS Framework classes so that the menus can be styled on the fly using ThemeRoller — try changing each menu’s theme with the “Switch Theme” button.

By default, the menu script will transform an unordered list of links into a simple dropdown menu:

Demo Page

When the menu content contains nested lists, by default it creates an iPod-style menu:

Demo Page

NOTE: If you’re familiar with our original iPod-style menu, we’ve made a few updates:

  • scrollbars are only applied to child menus that overflow the container, not to the menu container itself.
  • two ways to navigate: by default a “back” link appears in a footer that allows navigation to the previous (parent) menu, or set options to show a full breadcrumb above the menu that tracks your place in the navigation structure with a link for each level in the menu. Link text is configurable for both (see options below).
  • smoother slide transitions when navigating forward and back.

When you configure a menu with the “flyout” option set to true, the script formats a hierarchical list as a flyout:

Demo Page

All menus include:

  • ARIA menu roles and states as specified for menu widgets (learn more about ARIA best practices). We followed the WAI ARIA recommendations and validated the markup, but there’s always room for improvement.
  • support for jQuery UI CSS Framework classes, so they can be styled using ThemeRoller.
  • collision detection, which may be optionally turned off. Dropdown and iPod-style menus will flip up if there is not enough room to appear below; each child menu within a flyout will flip up or back when they reach the edge of the browser window.
  • rudimentary key controls, which include the ability to manipulate arrow keys for menu navigation, the enter key to select a node or navigate the iPod menu, and the escape key to close. (UPDATE: key controls now work in IE 6-8 and Safari. Thanks, Ian!)

How it works

Permalink to 'How it works'

Required markup

Permalink to 'Required markup'

The menu plugin was written with progressive enhancement in mind: first mark up a basic and functional unordered list of links, then layer on complex styles and functionality. (Ideally the advanced functionality will only be applied if the device is capable of rendering it correctly. We’ve developed a capabilities test to ensure that the appropriate experience is delivered to the a device — read more at A List Apart.)

List markup must conform to the following format to work with this plugin:

<ul>
  <li><a href="#">Menu option</a></li>
  <li><a href="#">Menu option</a></li>
  <li><a href="#">Menu option</a></li>
  <li><a href="#">Menu option</a></li>
</ul>

*  *  *

<ul>
  <li><a href="#">Menu option</a></li>
  <li><a href="#">Menu option</a></li>
  <li><a href="#">Menu option</a>
    <ul>
      <li><a href="#">Child menu option</a></li>
      <li><a href="#">Child menu option</a></li>
      <li><a href="#">Child menu option</a></li>
    </ul>
  </li>
  <li><a href="#">Menu option</a></li>
</ul>

Run the script on DOM ready

Permalink to 'Run the script on DOM ready'

Calling the menu plugin departs slightly from how jQuery normally works. Instead of “find something, act on it,” you attach the plugin to the link or button selector and pass in the content as an option. We deviated from the standard format so that we could reference the link/button with low overhead, and also make it possible to use content that’s loaded to the page via AJAX.

As shown below, all options are passed in using object notation (see full list of options below):

$(document).ready(function(){
    $('#myMenuButton').menu({
        content: $('#myContent').html(),
        maxHeight: 180,
        positionOpts: { offsetX: 10, offsetY: 20 },
        showSpeed: 300
    });
});

Configurable options

Permalink to 'Configurable options'

Default values are next to each option:

  • content: null - the menu content. It must be passed in as an option and formatted as an unordered list with anchor tags (links) for the nav options (see an example)

  • width: 180 - width of menu container. Required for hierarchical menus (flyout, ipod) to calculate widths of child menus

  • maxHeight: 180 - maximum height of menu (if ipod-style, height does not include breadcrumb, which can vary in height depending on content)

  • positionOpts (defaults listed below) - location and orientation of the menu, relative to the button/link used to open it

    • posX: ‘left’ - left side of the menu aligned with a side of the button, left or right
    • posY: ‘bottom’ - top of the menu aligned with a either top or bottom of the menu
    • offsetX: 0 - number of pixels to offset the menu left/right
    • offsetY: 0 - number of pixels to offset the menu top/bottom
    • directionH: ‘right’ - horizontal direction in which the menu will open, to the right or left
    • directionV: ‘down’ - vertical direction in which the menu will open, up or down
    • detectH: true - do horizontal collision detection
    • detectV: true - do vertical collision detection
    • linkToFront: false - set to “true,” this option will create a clone of the button and place it over the menu for an overlapping visual effect
  • showSpeed: 200 - speed to show/hide the menu in milliseconds

  • callerOnState: ‘ui-state-active’ - class to change the appearance of the link/button when the menu is showing

  • loadingState: ‘ui-state-loading’ - class added to the link/button while the menu is created

  • linkHover: ‘ui-state-hover’ - class for menu option hover state

  • linkHoverSecondary: ‘li-hover’ - alternate hover class, may be used for multi-level menus

Hierarchical (iPod-style and flyout) menu defaults

  • crossSpeed: 200 - transition effect speed for multi-level menus
  • crumbDefaultText: ‘Choose an option:’ - text that appears in the ipod-style footer before a child menu is opened
  • backLink: true - when set to “true”, this option shows a ‘back’ link under the menu instead of a full breadcrumb in the ipod-style menu
  • backLinkText: ‘Back’ - text for the back link (i.e., could also say ‘previous’)
  • flyOut: false - multi-level menus are ipod-style by default; set this option to “true” to override and make flyout the default instead
  • flyOutOnState: ‘ui-state-default’ - class added to an option when its child menu is showing
  • nextMenuLink: ‘ui-icon-triangle-1-e’ - class to style the link (specifically, a span within the link) used in the multi-level menu to show the next level
  • topLinkText: ‘All’ - text for the first breadcrumb that navigates back to the start of the ipod-style menu
  • nextCrumbLink: ‘ui-icon-carat-1-e’ - class for setting the background image that follows each breadcrumb link (currently a carat)

Known issues

Permalink to 'Known issues'

The following issues were found when we tested the menus in Firefox 2 & 3, Internet Explorer 6-8, Safari 3, and Opera 9.6:

  • We’re currently discussing with the jQuery Accessibility team whether the ARIA attributes we’ve used in this menu are in the right places. You can join the discussion here: Hierarchical menu ARIA markup – looking for input
  • The initial creation of the menu (first time user clicks the button) is very slow in IE 6.
  • Mouseover events are sometimes very slow in IE 6, but seem to work a little faster when using key controls.
  • Currently the “content” option is required, but it shouldn’t need to be. Ideally, this should just pull from the element referenced in the anchor’s href attribute.
  • If you spaz out and toggle arrow keys very quickly in the iPod-style menu, the menu may break and display a gray bar where the options should be.

Want to download this script?

Permalink to 'Want to download this script?'

This menu plugin is a working design prototype that you’re welcome to reuse and extend under the MIT and GPL open source licenses. It comes “as is” with the features demonstrated here, and with several known issues. If you think you can help on a particular issue, please submit a pull request and we’ll review it as soon as possible.

The demo code is available in a git repository, jQuery-Menu.

Earlier this year we contributed this plugin to the jQuery UI library. It’s currently being retooled into a more robust widget and is slated for upcoming release. You can track its progress at the jQuery UI Planning Wiki Menu planning page, or better, post feedback or share your own ideas about the jQuery UI planning process by joining the development and planning wiki.

Thanks for the helpful feedback!

Permalink to 'Thanks for the helpful feedback!'

We’re no longer taking comments on this post because we’ve handed off development of this widget to the jQuery UI development team. The menu is currently being re-factored and is slated for an upcoming jQuery UI release.

All blog posts