Keyboard Parity Is a Product Contract
Keyboard parity is a product contract
Keyboard parity does not mean "the web app copied every terminal key." It means every durable user intention can be completed without a pointer.
That distinction matters. A TUI has modes and pane-local state that do not map cleanly to a browser: terminal scroll, ratatui focus state, editor escape handling, virtualized row motion. Copying those one-for-one can create fake parity while leaving real mouse-only work behind.
The useful test
Ask this instead:
Can a keyboard-only user discover, reach, perform, and recover from the same product action?
That creates four obligations:
- Discover: help, command palette, visible focus, labels.
- Reach: Tab order, roving focus, shortcuts, palette.
- Perform: Enter/Space or a bound action, not only pointer click.
- Recover: undo, cancel, escape, focus return.
The first mxr web app audits showed how easy it is to satisfy only part of this. A shortcut can exist while the visual control still looks inert. A command palette can list actions while a chart drilldown remains pointer-only. A hover button can have a keyboard equivalent but still be invisible to Tab exploration.
Registries help, but do not prove parity
The Shared Action Registry is the right architecture for durable app actions. It prevents command palette, keymap, help dialog, status hints, and keybindings settings from drifting.
But it is not enough by itself. Page-local motion belongs with the page that owns the focus model. Mailbox j/k, reader scroll, sidebar roving focus, and vim editor state are not global actions. They still need discoverability and tests, but forcing them into the registry would blur the architecture.
What to document honestly
Good keyboard docs separate:
- App-level actions: one registry, many invocation surfaces.
- Page-local navigation: owned by the component, surfaced in help.
- Native controls: rely on browser/Radix semantics where possible.
- Known debt: mouse-only charts, hidden hover affordances, unfocusable scroll panes.
The docs should also lock the decisions that make the parity promise concrete.
If the product says keyboard-native compose matters, then the default editor,
its Vim surface, and any intentional gaps belong in the project's explicit
decision record, not in reviewer folklore. See Locked Decisions in Docs.
The strongest docs say what is true now, what is a target, and where the target is not yet met. "Full keyboard model" is weaker than "registry-backed actions plus page-local keyboard navigation, with these known gaps."
See also
- Mxr — the concrete case that produced the lesson.
- Shared Action Registry — the architectural half of the solution.
- Client-Agnostic Cores — parity at the protocol/client boundary.