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.
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.
listboxA listbox widget will popup. Use for custom select/dropdown components that allow selection from a list of options.
treeA tree widget will popup. Use for hierarchical selection like file browsers or nested menus.
gridA grid widget will popup. Use for date pickers, data grids, or spreadsheet-like selection interfaces.
dialogA 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

