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.

JavaScripting
JavaScripting Team
·5 min read
Accessible JS Menu - JavaScripting
Quick AnswerDefinition

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.

HTML
<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>
CSS
/* 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; }
JavaScript
// 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. 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. 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. 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. 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. 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.
Pro Tip: Prefer semantic HTML and ARIA semantics over heavy JavaScript if possible.
Warning: Never rely on mouse events alone; keyboard navigation must be supported.
Note: ARIA state must reflect actual visibility to avoid confusing users.

Prerequisites

Required

  • HTML/CSS fundamentals and modern JavaScript (ES6+)
    Required
  • A modern browser with DevTools (Chrome/Edge/Firefox)
    Required
  • Basic command-line knowledge
    Required

Optional

Keyboard Shortcuts

ActionShortcut
Move focus to next itemWhen a menu has focusDown Arrow
Move focus to previous itemWhen a menu has focusUp Arrow
Activate/select itemActivate focused menuitem
Open submenu (if present)Open nested menusRight Arrow
Close submenu or menuClose current menu levelEsc

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