anomalyco / anomalyco/opencode

Accessibility: add screen-reader-friendly TUI mode (configurable banner, animations, footer, alt-screen)

Open
#39,368 7 comments 0 reactions 1 assignee View on GitHub

@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:

  1. The startup banner / logo / ASCII art is hardcoded in the TUI client.
  2. The in-place line-rewrite spinners / animations are hardcoded. The only control is the app_toggle_animations keybind at runtime, which resets on every restart.
  3. 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.
  4. 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, or linear_mode option.

What I have already configured in ~/.config/opencode/tui.json, which works:

  • mouse: false — terminal native selection works
  • diff_style: "stacked" — single-column diffs
  • attention block — desktop + sound notifications on completion
  • scroll_speed: 1 and scroll_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: true implies banner: false, animations: false, footer: false, diff_style: "stacked", mouse: false. Individual toggles are still respected and can override.
  • The screen_reader mode 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.json to include the new fields and remove additionalProperties: false for them.
  • Document the trade-offs in https://opencode.ai/docs/tui and note that /export remains 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 the screen_reader mode.

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 as mouse: false.)
  • CLAUDE_CODE_DISABLE_MOUSE_CLICKS=1 — "Disable mouse click/drag/hover in fullscreen mode while keeping wheel scroll." (opencode could expose this as mouse_clicks: false or a sub-key under mouse.)
  • 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: false hygiene.
  • Existing configurable keys used as a baseline: mouse, diff_style, attention.*, scroll_*, keybinds.* — see https://opencode.ai/docs/tui and the schema at https://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-reader in 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.