anomalyco / anomalyco/opencode
Accessibility: add screen-reader-friendly TUI mode (configurable banner, animations, footer, alt-screen)
@simonklee is already working on this.
Since Jul 28, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Feature hasn't been suggested before.
- I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request
Problem
I'm a screen reader user (NVDA on Windows 11 and on Linux/Ubuntu) and I can't use the TUI effectively because four visual elements have no off-switch in tui.json, env vars, or the plugin API:
- The startup banner / logo / ASCII art is hardcoded in the TUI client.
- The in-place line-rewrite spinners / animations are hardcoded. The only control is the
app_toggle_animationskeybind at runtime, which resets on every restart. - The status footer (Context / token count / % used / $ spent) is not in the TUI schema. The schema is
additionalProperties: false, so I cannot even add the field without a schema change. - The TUI uses the alternate screen buffer, so the terminal emulator's own scrollback is empty during a session. NVDA's object navigator and review cursor can only see what is currently painted, not the chat history above. There is no
scrollback,no_alt_screen, orlinear_modeoption.
What I have already configured in ~/.config/opencode/tui.json, which works:
mouse: false— terminal native selection worksdiff_style: "stacked"— single-column diffsattentionblock — desktop + sound notifications on completionscroll_speed: 1andscroll_acceleration.enabled: false— linear, non-accelerated scrolling
Repro: I open the TUI on Windows Terminal and on GNOME Terminal with NVDA running. The banner and footer are announced on every screen refresh. When a tool runs, the spinner rewrites the same line and NVDA reports duplicated content. When I scroll up past the top of the chat panel with NVDA's object navigator, I hit a hard wall — the previous turns exist in the TUI's memory but not in the screen buffer. The same terminal session with claude --ax-screen-reader reads cleanly as flat linear text, which is what I want.
The plugin API can't help me here: plugins run in the Node server and only hook tool.execute.*, chat.*, shell.env, etc. They have no access to the Solid.js TUI client where the banner, spinners, footer, and alt-screen live.
Proposed Solution
Extend tui.json with the following fields, all defaulting to current behavior (no breaking change):
{
"$schema": "https://opencode.ai/tui.json",
"banner": false, // suppress startup ASCII art
"animations": false, // disable in-place spinners persistently
"footer": false, // hide the context/tokens/cost/% status bar
"screen_reader": true // single opt-in: enables all of the above,
// plus diff_style: "stacked" and mouse: false,
// and disables the alternate screen buffer so
// NVDA can read the terminal scrollback
}
screen_reader is the single switch that flips the whole experience; the three individual toggles exist so non-screen-reader users who simply dislike the noise can use them in isolation.
The same three configuration layers Claude Code uses would help adoption:
- CLI flag:
opencode --screen-reader - Environment variable:
OPENCODE_SCREEN_READER=1 - Settings key:
screen_reader: true(already shown above)
Flag > env var > settings, in that order, matching Claude Code's precedence.
Use Cases
- Screen reader users (NVDA, JAWS, VoiceOver, Orca) who need linear, banner-free, animation-free, scrollback-friendly output.
- Sighted users who simply find the banner / spinners / footer noisy and want to turn them off.
- Streamers and presenters recording their terminal who don't want the banner in the recording.
- Users on slow SSH sessions over high-latency links, where every screen redraw with rewritten spinner text is wasted bandwidth.
Implementation Notes
- Defaults:
banner: true,animations: true,footer: true,screen_reader: false. No behavior change for existing users. screen_reader: trueimpliesbanner: false,animations: false,footer: false,diff_style: "stacked",mouse: false. Individual toggles are still respected and can override.- The
screen_readermode that disables the alternate screen buffer is the only durable fix for the NVDA scrollback problem; the individual toggles are a smaller, easier first step that mitigates the symptoms. If a full renderer switch is out of scope for the first PR, the three individual toggles can ship independently. - Update the published schema at
https://opencode.ai/tui.jsonto include the new fields and removeadditionalProperties: falsefor them. - Document the trade-offs in
https://opencode.ai/docs/tuiand note that/exportremains the canonical way to read a full session transcript in a real editor with a screen reader. - The TUI client renders the banner, spinners, and footer. Plugins cannot affect these because the plugin API runs in the server, not in the TUI client. This needs to be a core change.
Reference: how Claude Code solved the same problem
Claude Code's settings schema (https://json.schemastore.org/claude-code-settings.json) documents seven accessibility-related environment variables. The most directly relevant for the NVDA scrollback problem is:
CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1— "Disable alternate screen buffer rendering. When set to 1, keeps conversation in native scrollback instead of fullscreen renderer." This is exactly the toggle needed for NVDA's object navigator and review cursor to reach the conversation history. Equivalent opencode option suggested in the proposed solution above as part of thescreen_readermode.
Claude Code also ships a dedicated screen-reader mode since v2.1.181, exposed three ways:
- CLI flag:
claude --ax-screen-reader - Environment variable:
CLAUDE_AX_SCREEN_READER=1 - Settings key:
axScreenReader: true
From their CLI reference: "Render screen-reader friendly output: flat text without decorative borders or animations. Forces the classic renderer, so the tui setting has no effect." The classic renderer is the escape hatch that makes the alternate screen buffer / NVDA problem go away by construction. A comparable design in opencode would be a strong fit.
Additional a11y toggles in Claude Code worth porting
These six more env vars from the same schema are all reasonable opencode candidates alongside the three primary toggles already proposed:
CLAUDE_CODE_ACCESSIBILITY=1— "Keep native cursor visible for screen magnifiers and assistive tools."CLAUDE_CODE_NATIVE_CURSOR=1— "Show the terminal's own cursor at the input caret instead of a drawn block."CLAUDE_CODE_DISABLE_MOUSE=1— "Disable mouse tracking in fullscreen mode." (opencode equivalent already exists asmouse: false.)CLAUDE_CODE_DISABLE_MOUSE_CLICKS=1— "Disable mouse click/drag/hover in fullscreen mode while keeping wheel scroll." (opencode could expose this asmouse_clicks: falseor a sub-key undermouse.)CLAUDE_CODE_DISABLE_VIRTUAL_SCROLL=1— "Disable virtual scrolling in fullscreen mode." (Useful for screen-reader users who want the full document in the screen buffer rather than virtualized.)CLAUDE_CODE_ALT_SCREEN_FULL_REPAINT=1— "Force a full-screen repaint on every frame in fullscreen mode." (May interact with the #38595 overlap bug; could be a workaround until the footer rendering is fixed.)
Anecdotal: numeric selection in Claude Code screen-reader mode
In my own use of Claude Code with --ax-screen-reader (or CLAUDE_AX_SCREEN_READER=1) enabled, I have observed that slash command menus and picker UIs can be selected by typing the item's number directly, on top of the normal arrow-key navigation. I could not find this in Claude Code's published documentation or in the settings schema, so it may be an undocumented side effect of screen-reader mode, a misperception on my part, or behavior that varies by version. If a maintainer can confirm the behavior, it would be the strongest reference for a parallel feature in opencode's screen_reader mode. If it cannot be confirmed, the case still stands on general TUI a11y practice — fzf, lazygit, and gum all support numeric shortcuts for menu items, which is the established pattern for screen-reader-friendly selection without focus tracking. Suggested opencode shape: a number_shortcuts: true (default false) option under screen_reader, or accept digit prefixes natively in the slash command and picker UIs whenever screen_reader: true.
Related
- #38393 "fix(a11y): expose streaming assistant content to screen readers" — open, in progress, directly overlaps with this request.
- #36165 "Accessibility: Slash-command popup menu options are hidden from screen readers" — open.
- #38595 "tui: update preflight footer renders overlapping frames" — open; the footer itself has rendering bugs on top of being non-toggleable.
- #38860 "Mouse tracking state not reset on exit (/exit)" — open; related to
mouse: falsehygiene. - Existing configurable keys used as a baseline:
mouse,diff_style,attention.*,scroll_*,keybinds.*— seehttps://opencode.ai/docs/tuiand the schema athttps://opencode.ai/tui.json.
Environment
- Screen reader: NVDA (latest stable)
- OSes tested: Windows 11 with Windows Terminal, Linux (Ubuntu) with GNOME Terminal
- opencode: latest at time of filing
- Compared against: Claude Code v2.1.181+ with
--ax-screen-readerin the same terminal + NVDA setup
Live reproduction while drafting this issue
While writing this very issue in the TUI, the status footer described above (context percentage, accumulated session cost, and LSP server status) is rendered into the right margin of the response area. When the response contains wrapped markdown bullets, the wrapped text and the footer share the same lines and become unreadable. This is the same overlap reported in #38595 and shows that the footer is not just a preference issue but an active readability blocker even for the person writing this report. Adding it to the list of reasons a footer: false toggle is needed.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.