JavaScript Menu: Accessible Keyboard Navigation Guide
A comprehensive tutorial on building accessible, keyboard-navigable JavaScript menus using semantic HTML, ARIA roles, and robust focus management. Learn patterns, code samples, and debugging tips to ensure cross-browser reliability and a great user experience.

A JavaScript menu is a keyboard-friendly UI component that exposes menu items in a hierarchical structure, activated by clicks or keys. According to JavaScripting, a robust menu uses semantic HTML, ARIA roles, and careful focus management to ensure accessibility. In this guide, you’ll learn patterns, code samples, and debugging tips to build reliable menus across browsers.
What is a JavaScript Menu?
A JavaScript menu is a UI component that presents a list of actions or navigation options in a structured, keyboard-accessible format. The goal is to provide predictable focus order, informative states, and support for assistive technologies like screen readers. A well-implemented menu uses semantic HTML, ARIA roles, and clear state changes to communicate with users who rely on keyboards or AT devices. The example below shows a simple top-level menu with a submenu. It demonstrates the use of role="menu", role="menuitem", and aria-expanded to reflect the open/closed state.
<nav aria-label="Main navigation" role="navigation">
<ul role="menu" aria-label="Main menu">
<li role="none"><button role="menuitem" tabindex="0">Home</button></li>
<li role="none" aria-haspopup="true" aria-expanded="false">
<button role="menuitem" aria-expanded="false" aria-controls="services">Services</button>
<ul id="services" role="menu" aria-label="Services submenu" hidden>
<li role="none"><button role="menuitem">Design</button></li>
<li role="none"><button role="menuitem">Development</button></li>
</ul>
</li>
<li role="none"><button role="menuitem">Contact</button></li>
</ul>
</nav>/* Basic visibility rules for submenus */
[hidden] { display: none; }
ul[role="menu"] { list-style: none; padding: 0; margin: 0; }
button[role="menuitem"] { background: transparent; border: none; padding: 8px 12px; cursor: pointer; }// Basic submenu toggle with ARIA state updates
document.querySelectorAll('[aria-haspopup="true"] > button').forEach(btn => {
btn.addEventListener('click', () => {
const submenuId = btn.getAttribute('aria-controls');
const submenu = document.getElementById(submenuId);
const isOpen = submenu && submenu.hidden === false;
if (submenu) submenu.hidden = isOpen;
btn.setAttribute('aria-expanded', String(!isOpen));
});
});Why it matters: Semantic structure and clear state signaling help screen readers understand the menu, while proper focus handling makes keyboard users productive from the start.
wordCountInBlock1
Steps
Estimated time: 60-120 minutes
- 1
Define HTML structure
Create a semantic container for the menu with appropriate ARIA roles. Build a top-level <nav> with a <ul role='menu'> and each item as a <button role='menuitem'>. For items with submenus, nest another <ul role='menu'> and manage aria-expanded/aria-controls.
Tip: Keep the DOM shallow enough to maintain fast focus changes. - 2
Add ARIA roles and attributes
Assign roles like menu, menuitem, and submenu containers. Use aria-expanded to indicate open state and aria-controls to reference submenus. Ensure each interactive button is keyboard-focusable and not disabled by default.
Tip: Validate with a11yDevTools to ensure proper ARIA relationships. - 3
Implement keyboard navigation
Write a keydown handler to move focus with Arrow keys, select with Enter/Space, and close with Escape. Support submenus with Left/Right arrows. Ensure the currently focused item is visibly distinct.
Tip: Test edge cases like first/last item wrap-around. - 4
Manage focus and open states
When opening a submenu, move focus to the first item. Trap focus within the open menu while it’s visible, and restore focus to the trigger when closed.
Tip: Avoid focus leaks when closing menus. - 5
Test across scenarios
Test mouse, keyboard, and assistive tech workflows. Validate that screen readers announce state changes and that navigation remains predictable after interactions.
Tip: Use screen readers (NVDA/VoiceOver) for realistic feedback.
Prerequisites
Required
- HTML/CSS fundamentals and modern JavaScript (ES6+)Required
- A modern browser with DevTools (Chrome/Edge/Firefox)Required
- Basic command-line knowledgeRequired
Optional
- VS Code or any code editorOptional
- Optional
Keyboard Shortcuts
| Action | Shortcut |
|---|---|
| Move focus to next itemWhen a menu has focus | Down Arrow |
| Move focus to previous itemWhen a menu has focus | Up Arrow |
| Activate/select itemActivate focused menuitem | ↵ |
| Open submenu (if present)Open nested menus | Right Arrow |
| Close submenu or menuClose current menu level | Esc |
Questions & Answers
What is a JavaScript menu?
A JavaScript menu is a navigable UI component that presents options in a structured, accessible way. It relies on semantic structure, ARIA roles, and keyboard navigation to ensure users can operate it with or without a mouse.
A JavaScript menu is a navigable UI that uses accessible structure and keyboard controls so users can operate it with or without a mouse.
Why are ARIA roles important for menus?
ARIA roles provide assistive technologies with explicit information about the element’s purpose and state. For menus, roles like 'menu' and 'menuitem' communicate structure and enable screen readers to announce focus and actions correctly.
ARIA roles tell assistive tech what each part of the menu is and how it behaves, so users get accurate feedback.
How do I test keyboard navigation effectively?
Test all keyboard interactions: moving focus with Arrow keys, activating items with Enter/Space, opening/collapsing submenus with Left/Right arrows, and closing with Escape. Validate focus remains within the menu during traversal.
Try navigating with your keyboard only to ensure everything is reachable and predictable.
Can I implement menus without a framework?
Yes. A accessible menu can be built with plain HTML, CSS, and vanilla JavaScript by carefully applying roles, states, and keyboard handlers. Frameworks can simplify patterns, but aren’t required for a solid baseline.
You don’t need a framework; plain HTML/CSS/JS can do it well if you follow accessibility patterns.
How should I handle clicking outside to close the menu?
Add a document-level click listener that closes the menu when a click occurs outside the menu container. Ensure clicking inside the menu doesn’t trigger a close.
Close the menu when you click outside, but ignore clicks inside the menu so it doesn’t shut unexpectedly.
What to Remember
- Build with semantic HTML and ARIA roles
- Use keyboard navigation for all interactions
- Keep focus trapped within open menus
- Test with real assistive technologies
- Document state transitions for maintainability