Loading Developer Playground

Loading ...

Skip to main content
ARIA ATTRIBUTEWidget Attributes

aria-haspopup

Indicates the availability and type of interactive popup element that can be triggered by an element. Announces to screen readers that activating this element will display additional content.

Value Type
true | false | menu | listbox | tree | grid | dialog
Common Use
Dropdown Menus
Pair With
aria-expanded

Overview

The aria-haspopup attribute indicates that the element can trigger a popup. It tells screen readers what type of popup will appear when the element is activated, helping users anticipate the behavior.

The value specifies the type of popup: menu for dropdown menus, listbox for select dropdowns, dialog for modals, etc. Using the correct value helps screen readers provide appropriate context.

Always Pair with aria-expanded

When using aria-haspopup, you should also use aria-expanded to indicate whether the popup is currently open. Together, they provide complete state information to assistive technologies.

Live Demo: aria-haspopup Types

aria-haspopup="menu"

Opens a menu. Screen reader: "File, menu button"

aria-haspopup="listbox"

Opens a listbox. Screen reader: "Select Color, popup button"

aria-haspopup="dialog"

Opens a dialog. Screen reader: "Open Settings, button, has popup dialog"

Screen reader behavior: Screen readers announce the popup type. For menu: "File, menu button, collapsed". For listbox: "Select Color, popup button". For dialog: "Open Settings, button, has popup dialog".

Attribute Values

menu(or true)

A menu widget will popup. Use for dropdown navigation menus, context menus, and action menus.

listbox

A listbox widget will popup. Use for custom select/dropdown components that allow selection from a list of options.

tree

A tree widget will popup. Use for hierarchical selection like file browsers or nested menus.

grid

A grid widget will popup. Use for date pickers, data grids, or spreadsheet-like selection interfaces.

dialog

A dialog will popup. Use for modal dialogs, confirmation prompts, or any overlay that requires user interaction.

false(default)

The element does not have a popup. This is the default when the attribute is not present.

Code Examples

Dropdown Menu

<!-- Dropdown Menu Button -->
<button 
  aria-haspopup="menu"
  aria-expanded="false"
  aria-controls="file-menu"
>
  File
</button>

<ul id="file-menu" role="menu" hidden>
  <li role="menuitem">New</li>
  <li role="menuitem">Open</li>
  <li role="menuitem">Save</li>
  <li role="none">
    <hr />
  </li>
  <li role="menuitem">Exit</li>
</ul>

<!-- Screen reader: "File, menu button, collapsed" -->
<!-- When expanded: "File, menu button, expanded, menu" -->

Custom Select/Listbox

<!-- Custom Select/Dropdown -->
<div class="custom-select">
  <button 
    aria-haspopup="listbox"
    aria-expanded="false"
    aria-labelledby="select-label selected-value"
    aria-controls="color-listbox"
  >
    <span id="select-label">Color:</span>
    <span id="selected-value">Red</span>
    <span aria-hidden="true">▼</span>
  </button>
  
  <ul 
    id="color-listbox" 
    role="listbox"
    aria-labelledby="select-label"
    hidden
  >
    <li role="option" aria-selected="true">Red</li>
    <li role="option" aria-selected="false">Blue</li>
    <li role="option" aria-selected="false">Green</li>
  </ul>
</div>

<!-- Screen reader: "Color: Red, popup button, collapsed" -->

Dialog Trigger

<!-- Button that opens a dialog -->
<button 
  aria-haspopup="dialog"
  onclick="openConfirmDialog()"
>
  Delete Account
</button>

<!-- When clicked, opens this dialog -->
<div 
  role="dialog" 
  aria-modal="true"
  aria-labelledby="dialog-title"
  hidden
>
  <h2 id="dialog-title">Confirm Deletion</h2>
  <p>Are you sure you want to delete your account?</p>
  <button>Yes, Delete</button>
  <button>Cancel</button>
</div>

<!-- Screen reader: "Delete Account, button, has popup dialog" -->

All Value Types

<!-- All aria-haspopup values -->

<!-- true (equivalent to "menu") -->
<button aria-haspopup="true">Options</button>

<!-- menu - opens a menu -->
<button aria-haspopup="menu">Edit</button>

<!-- listbox - opens a listbox/select -->
<button aria-haspopup="listbox">Choose Color</button>

<!-- tree - opens a tree widget -->
<button aria-haspopup="tree">Browse Files</button>

<!-- grid - opens a grid (like a date picker) -->
<button aria-haspopup="grid">Select Date</button>

<!-- dialog - opens a dialog -->
<button aria-haspopup="dialog">Open Settings</button>

<!-- false (default) - no popup -->
<button aria-haspopup="false">Regular Button</button>

React Components

// React Dropdown Menu Component
import { useState, useRef, useEffect } from 'react';

function DropdownMenu({ label, items }) {
  const [isOpen, setIsOpen] = useState(false);
  const menuRef = useRef(null);
  const buttonRef = useRef(null);
  
  // Close on outside click
  useEffect(() => {
    const handleClickOutside = (e) => {
      if (menuRef.current && !menuRef.current.contains(e.target)) {
        setIsOpen(false);
      }
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, []);
  
  // Handle keyboard navigation
  const handleKeyDown = (e) => {
    if (e.key === 'Escape') {
      setIsOpen(false);
      buttonRef.current?.focus();
    }
  };
  
  return (
    <div ref={menuRef} className="dropdown">
      <button
        ref={buttonRef}
        aria-haspopup="menu"
        aria-expanded={isOpen}
        aria-controls="dropdown-menu"
        onClick={() => setIsOpen(!isOpen)}
        onKeyDown={handleKeyDown}
      >
        {label}
        <span aria-hidden="true">{isOpen ? '▲' : '▼'}</span>
      </button>
      
      {isOpen && (
        <ul
          id="dropdown-menu"
          role="menu"
          onKeyDown={handleKeyDown}
        >
          {items.map((item, index) => (
            <li
              key={index}
              role="menuitem"
              tabIndex={-1}
              onClick={() => {
                item.onClick();
                setIsOpen(false);
              }}
            >
              {item.label}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

// Custom Select Component
function CustomSelect({ label, options, value, onChange }) {
  const [isOpen, setIsOpen] = useState(false);
  const selectedOption = options.find(o => o.value === value);
  
  return (
    <div className="custom-select">
      <button
        aria-haspopup="listbox"
        aria-expanded={isOpen}
        aria-labelledby="select-label"
        onClick={() => setIsOpen(!isOpen)}
      >
        <span id="select-label">{label}:</span>
        <span>{selectedOption?.label || 'Select...'}</span>
      </button>
      
      {isOpen && (
        <ul role="listbox" aria-labelledby="select-label">
          {options.map(option => (
            <li
              key={option.value}
              role="option"
              aria-selected={option.value === value}
              onClick={() => {
                onChange(option.value);
                setIsOpen(false);
              }}
            >
              {option.label}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

Best Practices

Use the specific value (menu, listbox, dialog, etc.) that matches your popup type

Always pair with aria-expanded to indicate open/closed state

Use aria-controls to reference the ID of the popup element

Ensure the popup has the correct role matching the aria-haspopup value

Implement proper keyboard navigation for the popup

Close popups on Escape key press

×

Don't use aria-haspopup="true" when a specific type applies

×

Don't forget to update aria-expanded when popup opens/closes

×

Don't use aria-haspopup on elements that don't trigger popups

×

Don't use aria-haspopup for tooltips—use aria-describedby instead

Common Use Cases

Navigation dropdown menus
Custom select/dropdown components
Context menus (right-click)
Modal dialog triggers
Date picker buttons
Color picker buttons
Toolbar dropdown buttons
Account/profile menu buttons

Related Attributes

Specifications & Resources