How to build Accessible Navigation Menus with the Popover API

Navigation menus are an essential part of every website. They're usually placed in the header and provide an overview of a website's page hierarchy. They enable users to gain an overview of the content and to quickly navigate to main sections of the website.

Many websites use fly-out (or drop-down) menus, where certain menu items open submenus on hover or click. Often, these menus offer very poor accessibility for screen reader and keyboard users. But we can do better!

I'll show you how to leverage the power of modern web features like the Popover API to easily create an accessible fly-out menu.

A hand holding up a compass for navigation. A rocky coast with trees is visible in the background. Photo: © Valentin Antonucci / pexels.com

What we want to achieve

Our navigation menu should be easy to use for different user groups:

  • The submenus should appear on top of the site content and should not disappear immediately after the mouse has left the clickable area.
  • Screen reader users require meaningful markup to understand and operate the navigation menu.
  • Keyboard users should be able to operate the menu using the keyboard alone.

Demo: Accessible Navigation Menu

I've created a CodePen demo of an accessible navigation menu using the Popover API. Be sure to open the demo on a desktop screen or tablet in landscape mode to see the fly-out menu. On smaller devices, the demo will display a mobile menu instead.

Screenshot of the accessible navigation menu demo page. The submenu of the menu item solutions is open. Photo: © Alexander Lehner

The navigation menu is easy to use for mouse, keyboard, and screen reader users. Let's examine the key elements.

Meaningful Markup

We use the nav element to convey the role of the navigation menu. The menu items are placed inside an unordered list (ul element). This allows assistive technologies to announce the number of items in the menu and provide corresponding navigation functionality.

<nav class="main-nav"> <ul> <li> <button popovertarget="nav-panel-solutions" type="button">Solutions</button> <div id="nav-panel-solutions" popover>...</div> </li> <li><a href="#">Products</a></li> <li>...</li> <li>...</li> </ul> </nav>

The list items either contain a direct link to a subpage, or they contain a button element that opens a submenu which uses the popover attribute. The button is connected to the popover content via the popovertarget attribute.

One of the great advantages of the Popover API: It automatically conveys to assistive technologies whether the submenu navigation is presently collapsed or expanded. You should read the article “On popover accessibility: what the browser does and doesn’t do” for more information.

Avoid the menu role

As you can see in my demo, I'm not using the menu and menuitem roles in my navigation menu. This is on purpose! These ARIA roles are only inteded for menu widgets that behave like native operating system menus.

Go read the article “Don’t Use ARIA Menu Roles for Site Nav” by Adrian Roselli for a detailed explanation.

Visual Placement

When a submenu is opened, it will automatically appear on the top layer, a separate layer that sits on top of all other layers displayed in the web document. You only need to take care of the placement and size of the submenu:

header nav.main-nav [popover] { top: var(--header-height); margin: 0; width: 100%; }

The default placement of popover content is in the center of the screen. To place our submenus right beneath the header, we set top to the height of the header and remove any margin. Next, we set width to 100% to make the submenu cover the whole width of the page.

Keyboard Operability

You can use the TAB key to focus the links and buttons in the navigation menu. After opening a submenu, its content will be next in the focus order. Pressing the ESC key will close the submenu and automatically return focus to the trigger button. All thanks to the mighty Popover API! 🤩

Light-dismiss of Submenus

Another baked in feature of the Popover API is light-dismiss: Clicking outside of the submenu area will close the submenu and return focus to the trigger element. No need to mess around with focusout events in JavaScript.

Useful Resources

Posted on