Skip to content

[BUG] Claude Code Desktop (Windows): slash command menu in the Code tab composer is silent for screen readers (NVDA) #94353

Description

@timveld

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • I am using the latest version of Claude Code

What's Wrong?

In Claude Code Desktop (the Code tab of Claude Desktop 1.52386.6.0 on Windows 11, Claude Code 2.1.270), the slash command menu that opens when typing / in the prompt composer (for example /rename) is completely silent for the NVDA screen reader. NVDA announces nothing when the menu opens, nothing while arrowing through it, and not which command is highlighted. Because ArrowUp/Down, Tab and Enter are taken over by the menu, a blind user cannot tell what Enter or Tab will do, so entering a command such as /rename New title becomes guesswork.

What Should Happen?

The composer exposes the menu as an autocomplete popup (ARIA 1.2 combobox pattern): NVDA announces that suggestions are available, reads the highlighted command and its description as the arrow keys move, and Enter/Tab behave predictably (WCAG 4.1.2 Name, Role, Value; 1.3.1 Info and Relationships; 4.1.3 Status Messages). The /resume and /rewind pickers in the same app already do this correctly.

Error Messages/Logs

None; this is an accessibility-tree problem.

Steps to Reproduce

  1. On Windows 11, start NVDA and open Claude Desktop → Code tab, with a Claude Code session.
  2. Focus the prompt composer and type /ren.
  3. Press ArrowDown/ArrowUp a few times, then press Enter or Tab.
  4. Observe: NVDA says nothing at any step; the menu is only visible on screen.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

No response

Claude Code Version

2.1.270 (Claude Code), bundled with Claude Desktop 1.52386.6.0

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Other (Claude Code Desktop, Code tab)

Additional Information

Root cause (from the shipped bundle resources/ion-dist/assets/v1/, minified names)

  1. Focus stays in the editor, and the editor does not reference the menu. The menu is a TipTap suggestion plugin (slash-command-suggestion, char:"/") in shared-13-D4O01Gj8.js. On mount the menu calls i.view.dom.focus(), so DOM focus stays in the ProseMirror contenteditable. That element never gets role="combobox", aria-expanded, aria-controls/aria-owns or aria-activedescendant: none of these attributes occur in that file. The popup element is appended straight to document.body (...ownerDocument.body.appendChild(o)), with no relationship to the editor in the accessibility tree.
  2. The highlighted row is only highlighted visually. In the lazy chunk ccc334009-BDcyQYO5.js (SlashCommandMenuPopupBody) each row is role:"menuitem", tabIndex:-1 with "data-highlighted": n ? "" : void 0. Rows have no id and no aria-selected, and never receive focus. The active row element is tracked (setActiveRowElement) only to scrollIntoView it. The container in shared-12-BzDv77xE.js is a role:"menu" div without an id or accessible name. Long lists are virtualised (getVirtualItems()), so the highlighted row may not be in the DOM until scrolled to.
  3. Navigation keys are swallowed before the screen reader can observe a change. The menu provider (OL, "useSlashCommandMenu", shared-12-BzDv77xE.js) adds a capture-phase keydown listener on the document (s.addEventListener("keydown",B,!0)). For ArrowUp/Down/Left/Right, Tab and Enter it calls preventDefault() and stopImmediatePropagation() and only updates React state (activeIndex). The suggestion plugin's onKeyDown in shared-13 also returns true for these keys while items are visible. Neither the caret, the focus nor aria-activedescendant changes, so NVDA has nothing to announce.
  4. No live-region fallback. Nothing announces "menu opened", the result count or the highlighted item. The only live region in this area is the skill argument hint (data-skill-arg-hint-sr); the "Type to filter" hint ($D) is a purely visual span.
  5. Enter/Tab depend on state the user cannot perceive. In OL's key handler Tab always activates the highlighted row; Enter either activates it or only closes the menu, depending on whether the user navigated with the arrows and whether the highlighted row matches the typed text (e.key==="Enter"&&D===0&&!O&&!A&&(...)?r()). Typed skills are also auto-resolved on a timer once a space follows a unique match (claudeai.slash_command_menu.auto_resolved).

Suggested fix

  • On the ProseMirror contenteditable while the menu is open: role="combobox" (or keep textbox and add the combobox attributes), aria-haspopup="listbox", aria-autocomplete="list", aria-expanded bound to the menu state, and aria-controls pointing to the menu's id (TipTap: editorProps.attributes, or set them in the suggestion's onStart/onExit).
  • Popup role="listbox" with an id and an accessible name ("Commands"); rows role="option" with a stable id and aria-selected; keep aria-activedescendant on the editor in sync with activeIndex (and the submenu index). The /resume picker (cli-resume-listbox, cli-resume-opt-N) and the /rewind list (rewind-opt-N) in c5610fbe3-0RHH64gR.js already implement this pattern.
  • Keep the active option in the DOM whenever it is referenced (virtualised list).
  • Add a polite live region with the result count when the menu opens or the filter changes, since aria-activedescendant changes on a contenteditable are not always announced by Chromium + NVDA.
  • Make Enter predictable: when the user has not navigated and the typed text is a complete command with arguments (/rename New title), submit the text as typed.

Notes

The root cause was established by reading the shipped bundle; the symptom is reproduced by the reporter with NVDA. No live instrumentation was run. Related report by the same reporter about focus handling in Claude Code Desktop: #94246.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions