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

Status Open
Reported on v2.1.270
Maintainer reply None cached
Activity 4 comments · opened Sep 14, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] 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.

View original on GitHub ↗

3 Comments

timveld · 29 days ago

Issue is still open in today's update Claude 2.110.0 (dfb2ba) 2026-09-15T05:50:55.000Z

kamalyaser31 · 14 days ago

Confirming this on Windows 11 with NVDA 2026.x and Claude Desktop (Code tab). While waiting for a proper fix, I built a local NVDA app module workaround. Here are the findings in case they help the fix:

  1. Focus: when the / menu opens, Chromium fires menuPopupStart for the role="menu" container (div.z-dropdown, portalled under div.cds-root). NVDA then moves its focus to that menu. It has no accessible name, so NVDA says nothing, and arrow keys no longer reach the composer.
  2. Highlight: rows are role="menuitem" with only data-highlighted. They have identical classes, no aria-selected and no active descendant, so no state change is exposed. The only signal that reaches the accessibility tree is the effective background-color in the IA2 text attributes: rgb(49,49,48) for the highlighted row and rgb(32,32,31) for the others (dark theme).
  3. Virtualization: the rendered row count grows as you arrow down (24 → 33 here), so any count a screen reader derives from the tree is wrong.

The workaround reads the highlighted row's background colour, which is fragile and depends on the theme. The real fix is still what this issue proposes: aria-activedescendant on the composer, role="option" with stable ids and aria-selected on each row, and an accessible name on the list.

CamdenBopp · 8 days ago

Confirming on macOS 27.0.1 with VoiceOver, Claude Desktop 2.26454.0 (Code tab). So this isn't Windows/NVDA-only.

Reproduced by typing / into the composer and pressing Down Arrow four times. VoiceOver announced nothing at any step, and both the VoiceOver cursor and keyboard focus stayed on the prompt text area.

The macOS accessibility tree (read via AXUIElement while the menu was open) matches the analysis above:

  • The composer (AXTextArea, description "Prompt") reports AXHasPopup = 0 and AXExpanded = 0, and has no AXOwns, AXControls or active-descendant attribute.
  • The menu exposes 26 AXMenuItem elements titled with the bare command name. AXDescription is empty, so the description text never reaches VoiceOver.
  • All rows report AXSelected = 0 after arrowing. The highlight shows only as the data-[highlighted]:bg-fill-ghost-hover class.
  • The rows' parents are plain AXGroups with no menu or list container, so there's no item count and nothing to interact into.

I couldn't find an older build to compare, so I can't say whether this ever worked.

Showing cached comments. Read the full discussion on GitHub ↗