menuitem
An option in a set of choices contained by a menu or menubar. Menuitems are actionable elements within menus that trigger commands or navigate to destinations.
Overview
The menuitem role represents a single action or command within a menu. When activated, a menuitem performs an action, such as executing a command, opening a dialog, or navigating to another resource.
Important: Menuitems must be contained within an element with role="menu" or role="menubar". Unlike buttons, menuitems use arrow keys for navigation between items, not Tab.
Menu vs. Navigation Links
ARIA menus are for application-style menus (like desktop apps), NOT for site navigation. For navigation menus, use <nav> with links. Use role="menu" only for action menus like File, Edit, or context menus.
Live Demo: Menu with Menuitems
Try it: Click the button or press Enter / β to open. Use β β to navigate, Enter to select, Escape to close.
Code Examples
Basic Menu Structure
<!-- Basic menu with menuitems -->
<div role="menu" aria-label="File menu">
<div role="menuitem" tabindex="-1">New</div>
<div role="menuitem" tabindex="-1">Open</div>
<div role="menuitem" tabindex="-1">Save</div>
<div role="separator"></div>
<div role="menuitem" tabindex="-1">Exit</div>
</div>
<!-- Important: Only one menuitem should be in the tab order -->
<!-- Use tabindex="-1" for all items, manage focus with JavaScript -->Dropdown Menu Pattern
<!-- Dropdown menu pattern -->
<div class="menu-container">
<button
id="menu-button"
aria-haspopup="menu"
aria-expanded="false"
aria-controls="file-menu"
>
File
</button>
<div
id="file-menu"
role="menu"
aria-labelledby="menu-button"
hidden
>
<div role="menuitem" tabindex="-1">New File</div>
<div role="menuitem" tabindex="-1">Open...</div>
<div role="menuitem" tabindex="-1">Save</div>
<div role="separator"></div>
<div role="menuitem" tabindex="-1">Print</div>
</div>
</div>
<script>
const button = document.getElementById('menu-button');
const menu = document.getElementById('file-menu');
button.addEventListener('click', () => {
const isOpen = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', !isOpen);
menu.hidden = isOpen;
if (!isOpen) {
// Focus first menuitem when opening
menu.querySelector('[role="menuitem"]').focus();
}
});
</script>Keyboard Navigation
<!-- Menuitem with full keyboard navigation -->
<div
role="menu"
aria-label="Actions menu"
onkeydown="handleMenuKeydown(event)"
>
<div
role="menuitem"
tabindex="-1"
id="menu-item-0"
aria-describedby="shortcut-0"
>
<span>Cut</span>
<span id="shortcut-0" class="shortcut">Ctrl+X</span>
</div>
<div
role="menuitem"
tabindex="-1"
id="menu-item-1"
aria-describedby="shortcut-1"
>
<span>Copy</span>
<span id="shortcut-1" class="shortcut">Ctrl+C</span>
</div>
<div
role="menuitem"
tabindex="-1"
id="menu-item-2"
aria-describedby="shortcut-2"
>
<span>Paste</span>
<span id="shortcut-2" class="shortcut">Ctrl+V</span>
</div>
</div>
<script>
function handleMenuKeydown(event) {
const menu = event.currentTarget;
const items = menu.querySelectorAll('[role="menuitem"]');
const currentIndex = Array.from(items).indexOf(document.activeElement);
switch (event.key) {
case 'ArrowDown':
event.preventDefault();
const nextIndex = (currentIndex + 1) % items.length;
items[nextIndex].focus();
break;
case 'ArrowUp':
event.preventDefault();
const prevIndex = currentIndex - 1 < 0 ? items.length - 1 : currentIndex - 1;
items[prevIndex].focus();
break;
case 'Home':
event.preventDefault();
items[0].focus();
break;
case 'End':
event.preventDefault();
items[items.length - 1].focus();
break;
case 'Enter':
case ' ':
event.preventDefault();
document.activeElement.click();
break;
case 'Escape':
// Close menu and return focus to trigger
closeMenu();
break;
}
}
</script>Disabled Menuitems
<!-- Menuitem with disabled state -->
<div role="menu" aria-label="Edit menu">
<div role="menuitem" tabindex="-1">
Undo
</div>
<div role="menuitem" tabindex="-1">
Redo
</div>
<div role="separator"></div>
<div
role="menuitem"
tabindex="-1"
aria-disabled="true"
class="menu-item-disabled"
>
Cut (no selection)
</div>
<div
role="menuitem"
tabindex="-1"
aria-disabled="true"
class="menu-item-disabled"
>
Copy (no selection)
</div>
<div role="menuitem" tabindex="-1">
Paste
</div>
</div>
<style>
.menu-item-disabled {
color: #999;
cursor: not-allowed;
}
</style>
<!-- Note: Disabled items should still be focusable but not activatable -->Submenu Pattern
<!-- Menuitem that opens a submenu -->
<div role="menu" aria-label="View menu">
<div role="menuitem" tabindex="-1">Zoom In</div>
<div role="menuitem" tabindex="-1">Zoom Out</div>
<div role="separator"></div>
<!-- Submenu trigger -->
<div
role="menuitem"
tabindex="-1"
aria-haspopup="menu"
aria-expanded="false"
aria-controls="appearance-submenu"
>
Appearance
<span aria-hidden="true">βΆ</span>
</div>
<!-- Submenu -->
<div
id="appearance-submenu"
role="menu"
aria-label="Appearance options"
hidden
>
<div role="menuitem" tabindex="-1">Light Theme</div>
<div role="menuitem" tabindex="-1">Dark Theme</div>
<div role="menuitem" tabindex="-1">System Default</div>
</div>
</div>
<!-- Keyboard navigation for submenus:
- ArrowRight: Open submenu
- ArrowLeft: Close submenu and return to parent
- Escape: Close current menu level -->React Component
// React Menu Component with Menuitem
import { useState, useRef, useEffect } from 'react';
function DropdownMenu({ label, items }) {
const [isOpen, setIsOpen] = useState(false);
const [activeIndex, setActiveIndex] = useState(-1);
const menuRef = useRef(null);
const buttonRef = useRef(null);
// Close menu when clicking outside
useEffect(() => {
const handleClickOutside = (e) => {
if (menuRef.current && !menuRef.current.contains(e.target)) {
setIsOpen(false);
setActiveIndex(-1);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const handleButtonKeyDown = (e) => {
if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
setIsOpen(true);
setActiveIndex(0);
}
};
const handleMenuKeyDown = (e) => {
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
setActiveIndex((prev) => (prev + 1) % items.length);
break;
case 'ArrowUp':
e.preventDefault();
setActiveIndex((prev) => (prev - 1 + items.length) % items.length);
break;
case 'Home':
e.preventDefault();
setActiveIndex(0);
break;
case 'End':
e.preventDefault();
setActiveIndex(items.length - 1);
break;
case 'Enter':
case ' ':
e.preventDefault();
if (activeIndex >= 0) {
items[activeIndex].onClick?.();
setIsOpen(false);
buttonRef.current?.focus();
}
break;
case 'Escape':
setIsOpen(false);
setActiveIndex(-1);
buttonRef.current?.focus();
break;
}
};
return (
<div ref={menuRef} className="relative">
<button
ref={buttonRef}
aria-haspopup="menu"
aria-expanded={isOpen}
onClick={() => setIsOpen(!isOpen)}
onKeyDown={handleButtonKeyDown}
className="px-4 py-2 bg-gray-800 text-white rounded"
>
{label}
</button>
{isOpen && (
<div
role="menu"
aria-label={`${label} menu`}
onKeyDown={handleMenuKeyDown}
className="absolute mt-1 bg-gray-800 rounded shadow-lg"
>
{items.map((item, index) => (
item.type === 'separator' ? (
<div key={index} role="separator" className="border-t border-gray-700 my-1" />
) : (
<div
key={index}
role="menuitem"
tabIndex={-1}
ref={(el) => activeIndex === index && el?.focus()}
onClick={() => {
item.onClick?.();
setIsOpen(false);
buttonRef.current?.focus();
}}
aria-disabled={item.disabled}
className={`px-4 py-2 cursor-pointer ${
activeIndex === index ? 'bg-blue-600' : 'hover:bg-gray-700'
} ${item.disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
>
{item.label}
{item.shortcut && (
<span className="ml-auto text-gray-400 text-sm">{item.shortcut}</span>
)}
</div>
)
))}
</div>
)}
</div>
);
}
// Usage
<DropdownMenu
label="File"
items={[
{ label: 'New', shortcut: 'Ctrl+N', onClick: () => console.log('New') },
{ label: 'Open', shortcut: 'Ctrl+O', onClick: () => console.log('Open') },
{ label: 'Save', shortcut: 'Ctrl+S', onClick: () => console.log('Save') },
{ type: 'separator' },
{ label: 'Export', disabled: true },
{ label: 'Exit', onClick: () => console.log('Exit') },
]}
/>Keyboard Support
Arrow keys move focus between menuitems. Focus should wrap from last to first (and vice versa).
Both keys should activate menuitems. The menu typically closes after activation.
Quick navigation to the beginning or end of the menu.
Closes the menu and returns focus to the trigger element (button/menuitem).
Right arrow opens a submenu, left arrow closes it and returns to the parent menu.
Typing characters can focus the first menuitem starting with that letter.
Best Practices
Always nest menuitems inside a menu or menubar element
Use arrow keys for navigation between menuitems, not Tab
Set tabindex="-1" on all menuitems and manage focus programmatically
Return focus to the menu trigger when the menu closes
Display keyboard shortcuts visually and describe with aria-describedby
Use role="separator" for visual dividers between menu sections
Don't use menu/menuitem for site navigation - use nav with links instead
Don't allow Tab to navigate between menuitems
Don't use menuitem outside of a menu or menubar container
Don't forget to close the menu and restore focus on Escape
Supported ARIA Attributes
aria-disabledIndicates the menuitem is not available for interaction
aria-haspopupIndicates menuitem opens a submenu (set to "menu")
aria-expandedIndicates if submenu is open (true/false)
aria-describedbyReferences element with additional description (e.g., shortcut)
aria-labelProvides accessible name if text content is insufficient
aria-labelledbyReferences element(s) that label the menuitem
aria-controlsReferences the submenu element this menuitem controls
aria-posinsetPosition of item in the set (for virtual scrolling)
Common Use Cases
Accessibility Notes
Focus Management
When the menu opens, focus should move to the first menuitem. When closed, focus must return to the element that triggered the menu. All menuitems should have tabindex="-1" so they're not in the natural tab orderβnavigation is done with arrow keys.
Screen Reader Announcements
Screen readers announce "menu" when entering and the menuitem name plus its position (e.g., "Save, 3 of 5"). If there's a keyboard shortcut, use aria-describedby to associate it with the menuitem so it's announced.
Visual Focus Indicator
The currently focused menuitem should have a clear visual indicator (highlighted background, outline, etc.). This is especially important for keyboard users who navigate with arrow keys. The indicator should have at least 3:1 contrast ratio.
Disabled Items
Disabled menuitems should use aria-disabled="true" and remain focusable (so users know they exist), but should not be activatable. They should have a visual style that indicates the disabled state (often grayed out).

