Introduction
Dropdown menus are common in modern interfaces. They provide access to navigation, account settings, filters, and contextual actions without permanently occupying screen space.
Although dropdowns look simple, implementing them accessibly requires careful attention to semantics, keyboard navigation, focus management, and dismissal behavior.
The first step is choosing the correct interaction pattern for the content.
Choose the Right Pattern
Not every control that looks like a dropdown should be implemented as a menu.
- Use a native <select> when users choose a value from a list.
- Use a disclosure button when users reveal additional content.
- Use a menu button when the options represent actions such as Edit, Duplicate, or Delete.
- Use a navigation structure when the panel contains ordinary navigation links.
These patterns have different keyboard and accessibility expectations. Adding role=”menu” to an ordinary list of links does not automatically make it more accessible.
Build a Simple Disclosure
For expandable content, React state and semantic HTML provide a useful starting point.
import { useState } from "react";
export function AccountPanel() {
const [open, setOpen] = useState(false);
return (
<div>
<button
type="button"
aria-expanded={open}
aria-controls="account-panel"
onClick={() => setOpen((value) => !value)}
>
Account
</button>
{open && (
<div id="account-panel">
<a href="/profile">Profile</a>
<a href="/settings">Settings</a>
</div>
)}
</div>
);
}
The button communicates its expanded state to assistive technology. Using a real button also provides standard keyboard activation without requiring custom event handling.
This is a disclosure component, not a complete ARIA menu. That distinction is intentional.
Manage Focus Correctly
When a menu opens, focus behavior should match the chosen pattern.
For a disclosure containing ordinary links, normal tab navigation is often appropriate. For a true ARIA menu, users generally expect focus to move to a menu item, with arrow-key navigation between items.
A full menu implementation may need to support:
- Arrow keys for navigating items.
- Home and End for moving to the first or last item.
- Escape for closing the menu.
- Enter or Space for activating the focused item.
- Returning focus to the trigger when appropriate.
Do not implement only some of these behaviors while declaring the component a complete menu. If the interaction requirements are complex, consider using a well-maintained accessible component library.
Support Escape
Escape should dismiss an open menu or disclosure when appropriate. For example, a disclosure can return focus to its trigger when Escape is pressed.
import { useRef, useState } from "react";
export function HelpDisclosure() {
const [open, setOpen] = useState(false);
const triggerRef = useRef(null);
function handleKeyDown(event) {
if (event.key === "Escape" && open) {
setOpen(false);
triggerRef.current?.focus();
}
}
return (
<div onKeyDown={handleKeyDown}>
<button
ref={triggerRef}
type="button"
aria-expanded={open}
onClick={() => setOpen((value) => !value)}
>
Help
</button>
{open && (
<div>
<a href="/documentation">Documentation</a>
<a href="/support">Contact support</a>
</div>
)}
</div>
);
}
This is a simplified example. The wrapper receives key events only when the event occurs within it. Production components may need additional handling for focus moving elsewhere, portals, or nested overlays.
Handle Outside Clicks
Many dropdowns close when a user clicks outside them. This is useful, but it should supplement keyboard interaction rather than replace it.
A common implementation uses a ref to identify the component boundary and a pointer event listener while the panel is open.
Remember to clean up event listeners when the component closes or unmounts. Test nested overlays carefully because a click inside another overlay may not behave like a click outside the entire interface.
Make the State Visible
A well-designed dropdown should provide:
- A visible focus indicator.
- Clear open and closed states.
- Adequate text and background contrast.
- A sufficiently large interaction target.
- Sensible positioning near the trigger.
- Layout behavior that works on narrow screens.
Do not rely exclusively on color to communicate the current state. Icons, labels, and other visual cues can make the interaction easier to understand.
Test Accessibility
Test the component with a keyboard, screen reader, browser zoom, and mobile-sized viewport.
Confirm that the trigger can be activated without a mouse, the current state is announced, focus behaves predictably, and the panel can be dismissed without trapping users.
Automated accessibility tools can identify some problems, but manual testing remains essential.
Conclusion
Accessible dropdowns begin with choosing the right interaction pattern. Use native controls when they fit, disclosures for expandable content, and full menu behavior when the interface genuinely needs it.
Accessibility depends on semantics, keyboard behavior, focus management, and visual feedback working together—not simply on adding ARIA attributes.