agentsclimarketplace

A11y menu

Skill xrnavigation/web-a11y-plugin/skills/a11y-menu

Web accessibility agent skills — 23 cite-backed skills covering APG widget patterns, audit tooling, ARIA guidance, cognitive accessibility, and more. Works with Claude Code, Codex CLI, and Gemini CLI.

Install
npx -y skills add xrnavigation/web-a11y-plugin --skill a11y-menu

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Guides accessible application menu and menu button implementation per APG patterns. Auto-invokes when creating menus, menubars, context menus, action dropdowns, or menu buttons. Critical distinction — site navigation is NOT a menu. Covers menu/menubar/menuitem roles, menuitemcheckbox, menuitemradio, and the menu button pattern.

SKILL.md

14.3 KB, as published. Nobody here has run it

Accessible Menu Patterns

Site navigation is NOT a menu. If you are building navigation links, stop here. Use <nav> with a list of links. See Section 1.

"Generally, don't use menu, menuitem, menubar, menuitemcheckbox, or menuitemradio. Only if you build something like Google Docs... these are warranted." — Marco Zehe, Mozilla (cited in Roselli, 2017)


1. Navigation Is NOT a Menu

This is the most common mistake in menu accessibility. ARIA menu/menubar/menuitem roles exist exclusively for application-style menus — the kind in desktop software (File, Edit, View). They are never for website navigation.

Why role="menu" Breaks Navigation

  1. Screen readers switch interaction modes. JAWS enters forms mode; NVDA enters focus mode. Arrow keys stop reading content and are intercepted for menu navigation. Users can no longer browse the page normally. (Tink.uk; Accessible Culture)

  2. Keyboard expectations change completely. role="menu" commits you to implementing Enter, Space, Down/Up/Left/Right Arrow, Home, End, Escape, and character-key navigation. Most site navs implement none of these. Users hear "menu," try menu keyboard commands, and nothing works. (Roselli, 2017)

  3. Navigation disappears from landmarks and link lists. The <nav> landmark is replaced by a menu widget. Links given role="menuitem" vanish from the screen reader links list (Insert+F7 in JAWS). Users lose two primary ways to discover site structure. (Roselli, 2017)

  4. Broken parent-child relationships. <ul role="menu"> without role="presentation" on <li> and role="menuitem" on <a> creates orphaned elements, violating WCAG SC 1.3.1. (Make Things Accessible)

Wrong vs Right

<!-- WRONG — navigation is not an application menu -->
<nav>
  <ul role="menu">
    <li role="menuitem"><a href="/about">About</a></li>
    <li role="menuitem"><a href="/news">News</a></li>
  </ul>
</nav>

<!-- RIGHT — semantic navigation, no ARIA needed -->
<nav aria-label="Main">
  <ul>
    <li><a href="/about">About</a></li>
    <li><a href="/news">News</a></li>
  </ul>
</nav>

For collapsible/dropdown navigation, use the disclosure pattern (<button> + aria-expanded), not menu roles. See Roselli, "Link + Disclosure Widget Navigation" (2019).

When to Use Menu Roles

Use menu/menubar/menuitem only when all of these are true:

  1. You are building an application (not a content website)
  2. Items are actions or functions (not links to other pages)
  3. You will implement the complete keyboard interaction model
  4. The pattern mirrors desktop application menus
  5. You want screen readers to enter forms/focus mode

Examples: rich text editor toolbar, email client actions, IDE menu bar, drawing app context menu.


2. Menu Button Pattern

The menu button is the most common legitimate use of ARIA menus. A button opens a popup menu of actions. (APG Menu Button)

Required Structure

<button
  aria-haspopup="menu"
  aria-expanded="false"
  aria-controls="actions-menu"
>
  Actions
</button>

<ul id="actions-menu" role="menu" aria-label="Actions" hidden>
  <li role="menuitem" tabindex="-1">Cut</li>
  <li role="menuitem" tabindex="-1">Copy</li>
  <li role="menuitem" tabindex="-1">Paste</li>
  <li role="separator"></li>
  <li role="menuitem" tabindex="-1">Delete</li>
</ul>

Button Requirements

PropertyValue
Element<button> (native, not <div>)
aria-haspopup"menu" (never changes)
aria-expanded"true" when open, "false" when closed
aria-controlsID of the menu element (optional but recommended)

Button Keyboard Interaction

KeyAction
Enter / SpaceOpens menu, focuses first item
Down Arrow(Optional) Opens menu, focuses first item
Up Arrow(Optional) Opens menu, focuses last item

Focus Management

  • Open: focus moves to the first menu item (or the currently checked item for persistent selections — Pickering)
  • Close (Escape or activation): focus returns to the button
  • Tab: exits the menu and closes it

Screen Reader Announcements

VoiceOver announces aria-haspopup values differently: "menu" produces "menu pop-up, button" while "true" produces "menu button, group." JAWS adds: "Press Space to activate the menu. Then navigate with arrow keys." (Matuzovic, 2023)


3. Required ARIA Structure

Menu / Menubar Skeleton

<!-- Menubar (persistent, horizontal) -->
<div role="menubar" aria-label="Text Editor">
  <!-- Top-level item with submenu -->
  <div role="menuitem" aria-haspopup="menu" aria-expanded="false" tabindex="0">
    File
    <ul role="menu" aria-label="File">
      <li role="menuitem" tabindex="-1">New</li>
      <li role="menuitem" tabindex="-1">Open...</li>
      <li role="menuitem" tabindex="-1">Save</li>
      <li role="separator"></li>
      <li role="menuitem" tabindex="-1">Exit</li>
    </ul>
  </div>

  <div role="menuitem" aria-haspopup="menu" aria-expanded="false" tabindex="-1">
    Edit
    <ul role="menu" aria-label="Edit">
      <li role="menuitem" tabindex="-1">Undo</li>
      <li role="menuitem" tabindex="-1">Redo</li>
    </ul>
  </div>
</div>

Key Rules

  • Roving tabindex: first menubar item gets tabindex="0", all others get tabindex="-1". Move tabindex="0" as focus changes. (APG Menubar)
  • Accessible name: every menu and menubar needs aria-label or aria-labelledby. (MDN; BOIA)
  • Valid children only: menu/menubar may only contain menuitem, menuitemcheckbox, menuitemradio, group, or separator. (MDN)
  • Disabled items: use aria-disabled="true" — items remain focusable but cannot be activated. (APG Menubar)
  • Submenu items: add aria-haspopup="menu" and aria-expanded to any menuitem that opens a submenu.
  • Avoid dual-purpose items: a menuitem should not both execute a function AND open a submenu. (APG Menubar)
  • Dialog items: append ellipsis ("...") to items that open dialogs.

Required States and Properties

PropertyWhereValue
aria-haspopupMenuitem with submenu"menu"
aria-expandedMenuitem with submenu"true" / "false"
aria-checkedmenuitemcheckbox / menuitemradio"true" / "false" (+ "mixed" for checkbox)
aria-disabledDisabled items"true"
aria-label / aria-labelledbymenu / menubarAccessible name
tabindexFirst menubar item: 0; all others: -1Roving tabindex

4. Menuitemcheckbox and Menuitemradio

menuitemcheckbox

A checkable menu item with three possible states. (MDN)

<ul role="menu" aria-label="View options">
  <li role="menuitemcheckbox" aria-checked="true" tabindex="-1">Show toolbar</li>
  <li role="menuitemcheckbox" aria-checked="false" tabindex="-1">Show status bar</li>
  <li role="menuitemcheckbox" aria-checked="mixed" tabindex="-1">Show rulers</li>
</ul>
  • aria-checked: "true", "false", or "mixed" (indeterminate)
  • Enter toggles checked state and closes the menu
  • Space toggles checked state and keeps the menu open (allows toggling multiple items)
  • All descendants are presentational — semantic elements inside lose their semantics

menuitemradio

A mutually exclusive option within a group. (MDN)

<ul role="menu" aria-label="Text size">
  <li role="group" aria-label="Font size">
    <li role="menuitemradio" aria-checked="false" tabindex="-1">Small</li>
    <li role="menuitemradio" aria-checked="true" tabindex="-1">Medium</li>
    <li role="menuitemradio" aria-checked="false" tabindex="-1">Large</li>
  </li>
</ul>
  • aria-checked: "true" or "false" only (no "mixed")
  • When activated, set own aria-checked="true" and all siblings' to "false"
  • Groups: use group role or separator to define separate radio groups within the same menu
  • Cannot contain interactive content or elements with tabindex

Visual Indicators (CSS)

Use CSS pseudo-elements — avoids adding DOM content that screen readers would announce redundantly. (MDN)

[role="menuitemcheckbox"][aria-checked="true"]::before { content: "\2713"; }
[role="menuitemradio"][aria-checked="true"]::before {
  background-color: currentColor;
  border-radius: 50%;
}

5. Keyboard Interaction Summary

Full keyboard model is in ${CLAUDE_SKILL_DIR}/references/keyboard-interaction.md.

KeyIn MenubarIn Menu (Popup/Submenu)
EnterOpens submenu (first item)Activates item, closes menu
SpaceOpens submenu (first item)Checkbox/radio: toggles (menu stays open). Other: activates, closes
Down ArrowOpens submenu (first item)Next item
Up Arrow(Optional) Opens submenu (last item)Previous item
Right ArrowNext menubar itemOpens submenu / moves right in menubar
Left ArrowPrevious menubar itemCloses submenu / moves left in menubar
EscapeCloses menu, returns focus to invoker
Home / EndFirst / last menubar itemFirst / last item in current menu

Critical: if you add role="menu", you must implement all of these. Partial implementation is worse than none — users hear "menu," try the expected keys, and nothing works. (Roselli, 2017; BOIA)


6. Common Mistakes

Detailed examples in ${CLAUDE_SKILL_DIR}/references/common-mistakes.md.

1. Using role="menu" for site navigation

The most widespread mistake. Triggers forms/focus mode, breaks Tab navigation, requires unimplemented keyboard handling. (Roselli, 2017; Make Things Accessible)

2. Incomplete ARIA role application

Adding role="menu" to <ul> without role="presentation" on <li> and role="menuitem" on children. Creates orphaned elements, violates SC 1.3.1. (Make Things Accessible)

3. Making links into menuitems

role="menuitem" on <a> strips link semantics. Screen readers no longer announce links; items vanish from link lists. If items are navigation destinations, they must be links, not menuitems. (Pickering; Roselli, 2017)

4. Missing accessible name on menu

A menu requires aria-label or aria-labelledby. Without it, screen reader users cannot identify the menu's purpose. (MDN; BOIA)

5. Missing keyboard interaction

Adding ARIA menu roles without implementing arrow keys, Home, End, Escape, character navigation. The roles promise behavior that is absent. Roselli: incorrect ARIA nesting "can wreak havoc and make your site navigation completely unusable." (Roselli, 2017)

6. Using the HTML <menu> element

The HTML <menu> element is deprecated and maps to <ul> semantics. The <menuitem> HTML element is also deprecated. No modern browser supports them reliably. These are unrelated to ARIA menu roles. Roselli: "Do not use them." (Roselli, 2023)


7. Cross-References

  • aria-decision-framework — use the decision tree before reaching for any ARIA role. Menu roles are Step 3 cases — only when no native HTML equivalent exists.
  • a11y-combobox — if users are selecting a value (not executing an action), use combobox or listbox, not menu.
  • a11y-dialog — menuitems that open dialogs should append "..." to their label.

For detailed reference material:

  • ${CLAUDE_SKILL_DIR}/references/menu-vs-navigation.md — full decision criteria
  • ${CLAUDE_SKILL_DIR}/references/keyboard-interaction.md — complete keyboard spec
  • ${CLAUDE_SKILL_DIR}/references/menu-button-pattern.md — menu button implementation details
  • ${CLAUDE_SKILL_DIR}/references/common-mistakes.md — anti-patterns with code examples
  • ${CLAUDE_SKILL_DIR}/references/sources.yaml — provenance for all cited sources

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.