터미널에서 실행된다는 이유만으로 접근 가능하다는 믿음은 착각이다. 이 글은 현대 TUI 프레임워크가 시각장애 사용자, 특히 스크린 리더 사용자에게 왜 더 큰 장벽이 되는지, 그리고 CLI와 전통적 도구들이 왜 더 잘 작동하는지를 설명한다.
2026년 1월 5일
비시각장애 개발자들 사이에는 끈질기게 이어지는 오해가 있다. 애플리케이션이 터미널에서 실행되면 본질적으로 접근 가능하다는 생각이다. 그 논리는 그래픽도 없고, 복잡한 DOM도 없고, WebGL 캔버스도 없으니, 내용은 그저 스크린 리더가 쉽게 해석할 수 있는 순수한 ASCII 텍스트일 것이라는 가정에 기반한다.
현실은 다르다. 대부분의 현대적 텍스트 사용자 인터페이스(TUI)는 형편없이 작성된 그래픽 인터페이스보다도 접근성에 더 적대적인 경우가 많다. 터미널에서 Developer Experience(DX)를 개선하려고 만들어진 바로 그 도구들, 즉 Ink (JS/React), Bubble Tea (Go), tcell 같은 프레임워크가 시각장애 사용자에게는 경험을 적극적으로 파괴하고 있다.
이 실패를 이해하려면, 흔히 “터미널 앱”이라는 말 아래 혼동되는 두 가지 서로 다른 개념을 구분해야 한다. 바로 CLI(Command Line Interface)와 TUI다.
CLI(스트림): 이것은 표준 입출력 모델(stdin/stdout)로 동작한다. 사용자가 명령을 입력하면 시스템은 그 결과를 아래에 덧붙이고, 커서는 아래로 이동한다. 이것은 선형적이고 시간순이다. 스크린 리더, 특히 Speakup 같은 커널 수준 리더에게는 이상적이다.
TUI(그리드): 이것은 터미널 창을 텍스트의 흐름이 아니라 2차원 픽셀 격자로 취급하며, 각 문자 칸을 하나의 픽셀처럼 다룬다. 시간적 흐름을 버리고 공간적 배치를 택하는 방식이다.
gemini-cli의 광기구체적인 예를 보자. Ink 프레임워크를 사용해 Node.js로 작성된 도구인 gemini-cli다. 겉으로 보기에는 단순한 채팅 인터페이스처럼 보인다. 하지만 내부에서는 Ink가 React 컴포넌트 트리를 터미널 격자 위에 맞춰 반영하려고 애쓰고 있다.
이 도구를 Speakup(리눅스)이나 NVDA(윈도우)와 함께 사용하면, 애플리케이션은 단순히 실패하는 것이 아니다. 사용자를 향해 적극적으로 스팸을 쏟아낸다.
프레임워크가 화면을 반응형 캔버스로 취급하기 때문에, 모든 업데이트는 다시 그리기를 유발한다. AI가 “생각 중”일 때 이 도구는 타이머나 스피너를 갱신한다. 이를 위해 하드웨어 커서를 타이머 위치로 옮기고, 새 시간을 쓰고, 다시 원래 자리로 되돌린다.
시각 사용자에게는 이 모든 일이 즉시 일어난다. 하지만 스크린 리더 사용자에게 들리는 것은 이런 식이다. “응답 중... 경과 시간 1초... 응답 중... 경과 시간 2초... [채팅 기록의 일부]... 응답 중...”
이것은 스크린 리더를 완전히 미치게 만든다. 커서는 상태 표시기, 스피너, 기록을 갱신하기 위해 화면 곳곳으로 순간이동한다. Speakup은 바로 그 밀리초 순간 커서 아래에 있는 것을 읽으려 한다. 결국 대화의 임의 조각들과 타이머 업데이트가 뒤섞여 들리게 되고, 사용자는 자신이 실제로 무엇을 입력하고 있는지에 집중할 수 없게 된다.
더 나쁜 상황도 있다. 지금까지 somehow Speakup으로는 어떻게든 버텼다고 가정해 보자. 그런데 NVDA로 작업을 좀 하고 싶다. 예를 들어 윈도우에서 발생하는 오류를 붙여넣고 싶을 수도 있다. 그래서 터미널을 열고, 리눅스 박스에 ssh로 접속하고, screen 세션에 붙고, 텍스트를 붙여넣는다.
결과는 스크린 리더(NVDA)의 즉각적인 충돌이거나 심각한 시스템 불안정성이다. 왜 그럴까? 문자를 하나 입력하거나 텍스트를 붙여넣을 때마다 애플리케이션은 상태 변화를 일으킨다. 프레임워크는 인터페이스를 다시 렌더링해야 한다고 판단한다. 대화 기록이 그 상태의 일부이기 때문에, 애플리케이션은 수천 줄의 텍스트에 대한 레이아웃을 즉시 다시 그리거나 재계산하려고 시도한다. 대화에 메시지가 많을수록 이 현상은 더 심해진다. 그리고 동적 콘텐츠 변경 안내를 피하기 위한 키 조합인 insert+5를 쓴다고 해서 이것을 피할 수도 없다.
게다가 단일 스레드 환경(Node.js 같은 곳)에서 실행되는 Ink 같은 프레임워크는 기록이 길어질수록 성능이 크게 저하된다. 큰 텍스트 블록을 붙여넣으면 시스템은 수천 줄에 대한 diff를 계산해야 한다.
이 때문에 입력 지연이 발생한다. 키를 누르고 나면 기다려야 한다. 문자 하나가 다시 화면에 나타나는 데 10초까지 기다릴 수 있다. 시스템은 사용자의 입력을 실제로 처리하기보다 화면을 어떻게 다시 그릴지 계산하느라 너무 바쁘기 때문이다.
nano, vim, menuconfig)비시각장애 개발자들은 자주 이렇게 묻는다. “TUI가 나쁘다면, 왜 nano, vim, menuconfig는 쓰나요?”
답은 이 도구들이 기본적으로 커서를 완벽하게 다루기 때문이 아니다. 답은 이 도구들이 커서를 완전히 숨길 수 있게 해주기 때문이다.
nano, vim)nano나 vim 같은 도구에서 사용성은 커서 위치를 추적하는 기능을 끄는 데 달려 있다. nano를 커서 위치를 표시하는 옵션(--constantshow 같은 것)과 함께 실행하거나, 특정 설정 없이 vim을 사용하면 경험은 망가진다.
커서가 보이고 추적이 활성화되면, Speakup은 문자 에코보다 커서 위치 갱신을 우선시한다. 글자 “a”를 입력했을 때 “a”를 듣는 대신 “2열”을 듣게 된다. “b”를 입력하면 “3열”이 들린다.
이 오래된 도구들이 성공하는 이유는 이런 잡음을 끌 수 있게 해주기 때문이다. 시각적 커서나 상태 표시줄 갱신을 억제하도록 설정할 수 있고, 그 결과 스크린 리더는 시끄러운 좌표 업데이트 대신 문자 입력 스트림에 의존하게 된다. 현대 프레임워크는 “커서 없음” 또는 “headless” 모드를 거의 제공하지 않는다. 그들은 시각적 커서가 필수라고 가정한다.
menuconfig)리눅스 커널의 menuconfig 같은 도구가 작동하는 이유는 엄격한 단일 열 집중을 강제하기 때문이다. 테두리와 제목이 있더라도 활성 영역은 세로 목록이다. 커서는 그 목록에 고정되어 있다. 시계를 갱신하려고 오른쪽 아래로 갔다가, 제목을 갱신하려고 왼쪽 위로 이동하지 않는다. 공간적 복잡성이 충분히 낮게 유지되기 때문에 스크린 리더가 결코 “길을 잃지” 않는다.
Irssi)Irssi는 접근 가능한 채팅의 황금 기준이지만, 그 이유는 운이 좋아서가 아니다. Irssi는 VT100 스크롤 영역을 활용하는 자체 렌더링 엔진 위에서 20년 넘게 다듬어져 왔다.
Irssi에 새 메시지가 도착하면:
터미널 드라이버에 이렇게 알린다. “1행부터 23행까지를 스크롤 영역으로 정의하라.”
명령을 보낸다. “위로 스크롤.” 그러면 터미널이 비트를 위로 이동시킨다.
그 영역의 맨 아래에 새 텍스트를 그린다.
중요한 점은, 입력 줄에 대한 간섭을 최소화하는 방식으로 이것을 처리한다는 것이다. 화면의 모든 문자를 수동으로 다시 쓰는 대신 터미널의 하드웨어 기능에 의존한다. 현대 프레임워크는 “diffing”을 통해 화면 상태를 비교하고 문자를 다시 쓰는 방식을 선호하면서 이런 하드웨어 기능을 무시하는데, 이 방식은 계산량이 더 많고 접근성에도 적대적이다.
Google과 gemini-cli 유지관리자들은 접근성을 신경 쓰는 척한다. 여기서 핵심은 “척한다”는 점이다. 저장소를 보면 Issue #3435와 Issue #11305 같은 치명적인 접근성 퇴행 문제가 방치된 채 썩어가고 있다. 논의도 없고, 로드맵도 없고, 수정도 없다. 더 나쁜 것은 이러한 접근성 실패를 추적하기로 했던 Issue #1553의 운명이다. 그것은 해결되지 않았다. 그냥 입막음당했다. 다음과 같은 상투적인 문구와 함께 봇이 자동으로 닫아 버렸다.
안녕하세요! 백로그를 관리 가능한 수준으로 유지하고 가장 활발한 이슈에 집중하기 위한 노력의 일환으로, 오래된 보고들을 정리하고 있습니다. 이 > 이슈는 한동안 활동이 없었던 것으로 보여 현재는 닫습니다.”
이것은 용납할 수 없다. 유지관리자들이 몇 달 동안 건드리지 않았다는 이유로 접근성 보고를 닫는 것은 “정리”가 아니다. 그것은 증거를 숨기는 일이다. 사실상 충분히 오래 무시된 버그는 존재하지 않는 것이나 마찬가지라고 말하는 셈이다. 프로젝트의 “닫힌 이슈” 지표는 좋아지겠지만, 실제 소프트웨어는 시각장애 사용자에게 여전히 사용할 수 없는 상태로 남는다.
터미널용 소프트웨어를 만들고 있고 접근성을 신경 쓴다면, 터미널을 캔버스처럼 다루는 선언형 UI 프레임워크 사용을 멈춰라.
“현대적” TUI 스택은 텍스트를 효율적으로 렌더링하는 기계의 능력을 희생시키면서, 개발자가 React 비슷한 코드를 작성하기 쉬운 방향으로 최적화되어 왔다.
애플리케이션이 사용자가 커서를 숨길 수 있도록 보장하지 못하거나, 스피너와 타이머를 보여주기 위해 공격적인 다시 그리기에 의존한다면, 당신은 접근 불가능한 도구를 만들고 있는 것이다.
시각장애 사용자에게는, 지연되고, 스팸을 뿌리고, 커서를 화면 곳곳에 흩뿌리는 “똑똑한” TUI보다, 단순하고 선형적인 CLI 스트림이 무한히 더 낫다.
writefreely로 발행됨