coming out of the discord convo about spinners and screen readers. tldr of the problem: spinners and live-redrawn prompts get re-announced on every repaint by voiceover/nvda/orca, which makes clack prompts effectively unusable with assistive tech. github hit the same wall with their cli and shipped static text + numbered inputs instead: https://github.blog/engineering/user-experience/building-a-more-accessible-github-cli/
detecting screen readers isn't viable (the reader runs client-side, so over ssh/containers there's zero signal, and local platform APIs are inconsistent + amount to sniffing disability status). so the proposal is an explicit opt-in following the emerging ACCESSIBLE env var convention (charmbracelet/huh already honors it in go land). nothing in the js prompt ecosystem does this yet, so clack adopting it likely sets the convention.
this issue is the coordination doc: design + checklist live here, chatter can stay on discord.
activation semantics
accessible mode is enabled when any of these resolve true, in priority order:
- explicit
accessible: true option on a prompt (or global setting)
ACCESSIBLE env var set to any non-empty value (0 and false explicitly disable)
- open question: should
!isTTY imply it, or stay a separate degradation path?
behavior per prompt
| prompt |
accessible behavior |
spinner |
print message once, plain still working: <msg> line every 30-60s (configurable), final done/error line. zero redraws |
progress-bar |
log at milestones (25/50/75/100) instead of continuous repaint |
task / task-log / stream |
audit for line rewrites, route through same static path |
select / select-key |
numbered list, read a number from stdin, re-prompt on invalid |
multi-select / group-multi-select |
numbered list, comma-separated numbers in, echo back parsed selection |
limit-options |
no pagination, print full list |
autocomplete |
degrade to plain "type your answer" + printed options (may split to follow-up) |
text / confirm / password / path / multi-line / date |
strip cursor-repositioning decorations, plain question in / plain input out, confirm becomes (y/n) |
note / box / log / messages |
plain text with clear begin/end delimiters, no box-drawing repaints |
implementation approach
intent is for accessible mode to be a distinct render path rather than if (accessible) conditionals scattered through every prompt. all prompts already flow through the shared render layer in core, so the accessible output should hang off that seam. keeping it isolated there also means future renderer work inherits it for free.
proposed plan
open questions
!isTTY implies accessible mode, or keep as separate degradation?
- global setting api shape (per-prompt option only vs
updateSettings-style global)
- interval default for spinner status lines (30s? 60s?)
- how custom renderers compose with accessible mode
coming out of the discord convo about spinners and screen readers. tldr of the problem: spinners and live-redrawn prompts get re-announced on every repaint by voiceover/nvda/orca, which makes clack prompts effectively unusable with assistive tech. github hit the same wall with their cli and shipped static text + numbered inputs instead: https://github.blog/engineering/user-experience/building-a-more-accessible-github-cli/
detecting screen readers isn't viable (the reader runs client-side, so over ssh/containers there's zero signal, and local platform APIs are inconsistent + amount to sniffing disability status). so the proposal is an explicit opt-in following the emerging
ACCESSIBLEenv var convention (charmbracelet/huh already honors it in go land). nothing in the js prompt ecosystem does this yet, so clack adopting it likely sets the convention.this issue is the coordination doc: design + checklist live here, chatter can stay on discord.
activation semantics
accessible mode is enabled when any of these resolve true, in priority order:
accessible: trueoption on a prompt (or global setting)ACCESSIBLEenv var set to any non-empty value (0andfalseexplicitly disable)!isTTYimply it, or stay a separate degradation path?behavior per prompt
spinnerstill working: <msg>line every 30-60s (configurable), final done/error line. zero redrawsprogress-bartask/task-log/streamselect/select-keymulti-select/group-multi-selectlimit-optionsautocompletetext/confirm/password/path/multi-line/date(y/n)note/box/log/messagesimplementation approach
intent is for accessible mode to be a distinct render path rather than
if (accessible)conditionals scattered through every prompt. all prompts already flow through the shared render layer in core, so the accessible output should hang off that seam. keeping it isolated there also means future renderer work inherits it for free.proposed plan
isAccessible()util in@clack/core+ resolution order testsaccessibleoption through shared prompt options incore/src/types.tsopen questions
!isTTYimplies accessible mode, or keep as separate degradation?updateSettings-style global)