Preflight Checklist
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
- On Windows 11, start NVDA and open Claude Desktop → Code tab, with a Claude Code session.
- Focus the prompt composer and type
/ren.
- Press ArrowDown/ArrowUp a few times, then press Enter or Tab.
- 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)
- 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.
- 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.
- 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.
- 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.
- 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.
Preflight Checklist
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 titlebecomes 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
/resumeand/rewindpickers in the same app already do this correctly.Error Messages/Logs
None; this is an accessibility-tree problem.Steps to Reproduce
/ren.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)slash-command-suggestion,char:"/") inshared-13-D4O01Gj8.js. On mount the menu callsi.view.dom.focus(), so DOM focus stays in the ProseMirror contenteditable. That element never getsrole="combobox",aria-expanded,aria-controls/aria-ownsoraria-activedescendant: none of these attributes occur in that file. The popup element is appended straight todocument.body(...ownerDocument.body.appendChild(o)), with no relationship to the editor in the accessibility tree.ccc334009-BDcyQYO5.js(SlashCommandMenuPopupBody) each row isrole:"menuitem", tabIndex:-1with"data-highlighted": n ? "" : void 0. Rows have noidand noaria-selected, and never receive focus. The active row element is tracked (setActiveRowElement) only toscrollIntoViewit. The container inshared-12-BzDv77xE.jsis arole:"menu"div without anidor accessible name. Long lists are virtualised (getVirtualItems()), so the highlighted row may not be in the DOM until scrolled to.OL, "useSlashCommandMenu",shared-12-BzDv77xE.js) adds a capture-phasekeydownlistener on the document (s.addEventListener("keydown",B,!0)). For ArrowUp/Down/Left/Right, Tab and Enter it callspreventDefault()andstopImmediatePropagation()and only updates React state (activeIndex). The suggestion plugin'sonKeyDowninshared-13also returnstruefor these keys while items are visible. Neither the caret, the focus noraria-activedescendantchanges, so NVDA has nothing to announce.data-skill-arg-hint-sr); the "Type to filter" hint ($D) is a purely visual span.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
role="combobox"(or keeptextboxand add the combobox attributes),aria-haspopup="listbox",aria-autocomplete="list",aria-expandedbound to the menu state, andaria-controlspointing to the menu's id (TipTap:editorProps.attributes, or set them in the suggestion'sonStart/onExit).role="listbox"with an id and an accessible name ("Commands"); rowsrole="option"with a stableidandaria-selected; keeparia-activedescendanton the editor in sync withactiveIndex(and the submenu index). The/resumepicker (cli-resume-listbox,cli-resume-opt-N) and the/rewindlist (rewind-opt-N) inc5610fbe3-0RHH64gR.jsalready implement this pattern.aria-activedescendantchanges on a contenteditable are not always announced by Chromium + NVDA./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.