why modern TUIs are a nightmare for accessibility — The Inclusive Lens

legendary, it’s text, so it’s accessible

There is a persistent misconception among visionary developers: if an application runs in a terminal, it is inherently accessible. The logic assumes that because there are no graphics, no complex DOM, and no WebGL canvas, the content is just raw ASCII text that a screen reader can easily parse.

The reality is different. Most modern text user interfaces (TUIs) are often more hostile to accessibility than poorly coded graphical interfaces. Tools designed to improve the developer experience (DX) in the terminal – frameworks like Ink (JS/React), Bubble Tea (Go), or Tcell – are actively destroying the experience for visually impaired users.

Vastu Dosh: Stream vs Grid

To understand the failure, we need to distinguish between two different concepts often found under “Terminal Apps”: CLI (Command Line Interface) and TUI.

  1. CLI (The Stream): It works on a standard input/output model (stdin/stdout). You type a command, the system adds the results below, and the cursor moves down. It is linear and chronological. For screen readers, especially kernel-level readers like SpeakUp, this is ideal.

  2. TUI (The Grid): This treats the terminal window not as a stream of text, but as a 2D grid of pixels, where each character cell is a pixel. This abandons temporal flow for spatial layout.

Case Study: The gemini-cli Madness

Let’s look at a concrete example: gemini-cliA tool written in Node.js using the Ink Framework. On the surface, it looks like a simple chat interface. But underneath, Ink is trying to condense a React component tree into a terminal grid.

When you use this tool with speakup (Linux) or NVDA (Windows), the application does not fail; It actively spams you.

Because the framework treats the screen as a responsive canvas, each update triggers a redraw. When the AI ​​is “thinking”, the tool updates the timer or spinner. To do this, it moves the hardware cursor to the timer location, writes the new time, and moves it back.

For a visually impaired user, this happens instantly. For a screen reader user, this is what you hear:
“Responding… 1 sec. Elapsed… Responding… 2 sec. Elapsed… [Fragment of chat history]…I am replying…”

This drives screen readers crazy. The cursor is teleporting across the screen to update the status indicator, spinner, and history. Speakup attempts to read whatever is under the cursor at that exact millisecond. You hear random snippets of conversation mixed in with timer updates, making it impossible to focus on what you’re actually typing.

Even worse, pretend that you’ve somehow done a good job with SpeakUp so far, but you want to do some work with NVDA. Maybe the error you’re getting is stuck on Windows. So you open your terminal, ssh into your Linux box, connect to your screen session and paste your text.

This may result in immediate crash of the screen reader (NVDA) or massive system instability. Why? Every time you type a character or paste text, the application triggers a state change. The framework decides that it needs to re-render the interface. Because the conversation history is part of that state, the application immediately tries to recreate or recalculate the layout for thousands of lines of text. The more messages there are in a conversation, the higher this will be. And no, you can’t avoid it by just using Insert+5, the key combo that’s supposed to avoid announcing dynamic changes of content.

lag loop

Additionally, frameworks like Ink that run on a single-threaded environment (like Node.js) suffer from massive performance degradation as the history grows. If you paste a large block of text, the system has to calculate the difference of thousands of lines.

This causes input lag. You press a key, and you wait. can wait till you 10 seconds To echo the same letter back. The system is too busy calculating how to reshape the screen to actually process your input.

Visionary developers often ask: “If TUIs are bad, why do you use nano, vim or menuconfig?”.

The answer is no, these tools handle cursors perfectly by default. The answer is that they allow you to hide cursor completely.

1. Hiding the cursor (nano, vim)

in devices like nano Or vimUsability depends on turning off features that track cursor position. if you run nano With options that show the cursor position (e.g. --constantshow), or if you use vim Without specific configuration, the experience is broken.

When the cursor is visible and tracking is active, SpeakUp prioritizes cursor location updates over character echoes. Instead of hearing the letter “A” when you type it, you hear “Column 2.” You type “B”, and you hear “Column 3”.

These older tools are successful because they allow you to disable this noise. You can configure them to suppress visual cursor or status bar updates, forcing the screen reader to rely on the character input stream instead of noisy coordinate updates. Modern frameworks rarely offer “no-cursor” or “headless” modes; They agree that a visual cursor is essential.

2. Single Column Focus (menuconfig)

Tools like Linux kernel menuconfig Work because they enforce a strict, single-pillar focus. Although there are borders and titles, the active area is a vertical list. The cursor remains pinned to that list. It doesn’t go to the bottom right to update a clock, then to the top left to update a title. Spatial complexity is kept low enough that the screen reader never gets “lost”.

Irsee is the gold standard for accessible chat, but not because of luck. Irssi was built over 20 years with a custom rendering engine that uses VT100 scrolling areas.

When a new message arrives in Irssi:

  1. This tells the terminal driver: “Define the scrolling area from lines 1 to 23.”

  2. This sends a command: “scroll up.” Moves the terminal bits up.

  3. This draws new text at the bottom of that area.

Importantly, it handles this in such a way that interference with the input line is minimized. It relies on the hardware capabilities of the terminal rather than manually retyping each character on the screen. Modern frameworks ignore these hardware features in favor of “disassembling” the screen state and rewriting characters, which is computationally heavy and unfavorable for accessibility.

The “Stale Bot” Excuse: A Case Study in Neglect

Google and the maintainers of gemini-cli pretend to care about accessibility. “Pretend” is the operative word here. If you look at the repository, important accessibility regressions like issue #3435 and issue #11305 have been left to rot. There is no discussion, no roadmap and no solutions. Even worse is the fate of issue #1553, which was supposed to track these access failures. It was not resolved; It became quiet. It was automatically closed by a bot with this general dismissal:

hello! As part of our effort to keep our backlog manageable and focused on the most active issues, we’re organizing older reports. It looks like this issue hasn’t been active for some time, so we’re closing it for now.

this is unacceptable. Closing an accessibility report because maintainers haven’t touched it for several months is not “tidying up” it; This is hiding evidence. This effectively says that if a bug is ignored long enough, it ceases to exist. This boosts the project’s “closed issues” metric while rendering the actual software unusable for blind users.

conclusion

If you’re building for the terminal and care about accessibility, stop using declarative UI frameworks that treat the terminal like a canvas.

The “modern” TUI stack is optimized for the developer’s ability to write React-like code at the expense of the machine’s ability to render text efficiently.

If you can’t guarantee that your application allows the user to hide the cursor, or if you rely on aggressive redrawing to show spinners and timers, you are building an inaccessible tool.

For a blind user, a dumb, linear CLI stream is infinitely better than a “smart” TUI that slows, spams, and scatters the cursor across the screen.



<a href

Leave a Comment