aria-haspopup might not do what you think it does

posted on

To kick off my new article series, #WebAccessibilityFails, I decided to focus on a bad practice I often see in main navigations during accessibility audits.

Developers like to put aria-haspopup on buttons inside main navigations to indicate that the buttons control subnavigations.

<nav>
    <ul>
      <li>
        <button aria-haspopup="menu">Products</button>
        <ul>
          <li><a href="#">Wood</a></li>
          <li><a href="#">Steel</a></li>
          <li><a href="#">Plastic</a></li>
        </ul>
      </li>
    </ul>
</nav>

The purpose of aria-haspopup

There is no particular problem with the aria-haspopup property itself, but when used alone without any extra work, it can confuse users or even prevent them from accessing the submenu. To understand why, let's first look at the definition.

[aria-haspopup] Indicates the availability and type of interactive popup element, such as menu or dialog, that can be triggered by an element. A popup element usually appears as a block of content that is on top of other content.

That sounds like the property is spot on in this case, but the next sentence is important.

Authors MUST ensure that the role of the element that serves as the container for the popup content is menu, listbox, tree, grid, or dialog, and that the value of aria-haspopup matches the role of the popup container.

aria-haspopup doesn't just indicate that the button controls any popup, but a specific type of popup. It can either be a menu, listbox, tree, grid, or a dialog. In the example above, the button controls an element with the list role. A list doesn't become a menu just because you use it inside a nav element. Also, it's likely that it shouldn't be a menu in the first place because a navigation is different from a menu. Here's the definition of the menu role.

A type of widget that offers a list of choices to the user.

A menu is often a list of common actions or functions that the user can invoke. The menu role is appropriate when a list of menu items is presented in a manner similar to a menu on a desktop application.

Here's the definition of the navigation role.

A collection of navigational elements (usually links) for navigating the document or related documents.

The problem is that we like to use the terms navigation and menu interchangeably to describe the same thing. So I understand where the confusion is coming from.
I guess there is a grey area, but in most cases the distinction is pretty straightforward:

Navigation = List(s) of links
Menu = Items that perform actions

Implications

I'm not just writing this post because using the property like that is wrong; I'm writing it because doing so can have serious implications for your users. In the definition of the menu role, it says:

To be keyboard accessible, authors SHOULD manage focus of descendants for all instances of this role, as described in Managing Focus.

If you don't implement specific keyboard shortcuts, users may not understand how to navigate or even access the items. Here's what happens if you access the example at the top of this post with different screenreaders:

VoiceOver, macOS 15.6.1, Safari:
When you access the button with the Tab key, you get: Products, menu pop-up button, list of 4 items.
The list items are accessible via Tab and virtual cursor

Talkback, Android 16, Chrome 145:
Announcement: Products menu pop-up button, double-tap to activate
The list items are accessible via swipe and touch.

JAWS 2026, Windows 11, Chrome 145:
Announcement: Products button menu, press Space to activate the menu, then navigate with arrow keys
JAWS switches from browse to focus mode. When you press the arrow keys, nothing happens. The links are accessible via Tab.

NVDA 2025.3.3, Windows 11, Firefox 147
Announcement: Products menu button, submenu
The list items are accessible via Tab and virtual cursor

Narrator, Windows 11, Edge:
Announcement: Products, button, expanded
The list items are accessible via Tab and virtual cursor

VoiceOver, iOS 18.6.2, Safari:
Announcement: Products, pop up button, menu pop up
The list items are accessible via swipe and touch.

Experienced users may expect certain shortcuts when they hear something like "menu pop-up". With JAWS, they don't even have to know how to control menus, because JAWS expects and announces specific keyboard shortcuts to work. If you don't provide them, users may have a harder time accessing the menu.

Alternative

Okay, so what can you do instead? Set aria-expanded="false" on the button and toggle its value on click to indicate whether the controlled element is open.

<nav>
    <ul>
      <li>
        <button aria-expanded="false">Products</button>
        <ul>
          <li><a href="#">Wood</a></li>
          <li><a href="#">Steel</a></li>
          <li><a href="#">Plastic</a></li>
        </ul>
      </li>
    </ul>
</nav>

What about aria-haspopup=true? Nope, sorry, because the value true defaults to menu.

Additional resources