
ariaNotify()의 유혹적인 노래
요약
WAI-ARIA 1.3 사양에 새롭게 정의된 ariaNotify() 메서드를 소개합니다. 기존의 복잡하고 까다로웠던 라이브 리전(live regions) 방식 대신, 프로그래밍 방식으로 스크린 리더의 내레이션을 직접 트리거할 수 있는 간결한 해결책을 제공합니다.
핵심 포인트
- ariaNotify()는 스크린 리더 내레이션을 프로그래밍 방식으로 트리거함
- 기존 aria-live 속성을 활용한 라이브 리전 구현의 복잡성을 해결
- 문자열과 설정 객체를 인자로 받아 직관적인 사용 가능
- 웹 접근성(Accessibility) 향상을 위한 강력한 도구
여러분 모두 저에게 이것에 대해 쿨하게 대해주겠다고 약속해 주셔야 합니다. 저는 아주 오랫동안 기다려온 새로운 웹 플랫폼 기능에 대해 말씀드리러 왔습니다. 이 기능은 더 나은 솔루션이 절실히 필요했던 사용 사례를 충족할 뿐만 아니라, 즉각적으로 이해 가능하면서도 기만적일 정도로 강력한 구문을 통해 이를 수행합니다. 맞습니다, 이것은 _개발자들의 캣닙 (developer catnip)_이며, 저 또한 이것을 정말 써보고 싶어 흥분했다는 점을 숨기지 않겠습니다. 하지만 그 직후 저는 이것을 서랍 속에 넣어두고 마음속에서 지워버리려고 스스로를 다독였습니다. 이 도구는 다른 방법으로는 도저히 해결할 수 없는 문제를 해결하기 위해, 정말로, 100퍼센트 필요한 상황에서만 사용되어야 하며, 심지어 "애초에 해당 기능을 구축하는 것에 반대하는 것"까지 포함될 정도입니다. 그러니 그냥 이것에 대해 쿨하게 대해주세요, 아셨죠? 알겠습니다.
Accessible Rich Internet Applications (WAI-ARIA) 1.3 Specification에 의해 정의된 완전히 새로운 ariaNotify() 메서드가 등장했습니다. 이 메서드는 스크린 리더 (screen reader)에서 프로그래밍 방식으로 내레이션을 트리거할 수 있는 수단을 제공합니다. 첫 번째 인수로 문자열을 받고, 두 번째 인수로 선택적인 설정 객체를 받습니다:
document.ariaNotify( "Hello, World." );
// 호출되면, 스크린 리더가 "Hello, World."를 내레이션합니다.
글로 읽기에는 매우 단순한 사용 사례에 대한 간단한 해결책처럼 보일 수 있지만, 역사적으로 이 문제는 ARIA의 **라이브 리전 (live regions)**을 약간 용도 외로 사용하여 해결할 수밖에 없었던 까다로운 문제였습니다. 즉, 라이브 리전과 그 단점들을 이해하는 것이 ariaNotify가 우리를 위해 무엇을 해주는지 이해하는 핵심입니다. 만약 이전에 라이브 리전을 다뤄본 적이 있다면, 아마 저 코드 스니펫을 보자마자 이 탭을 닫고, 현재 승리의 포즈를 취하며 방 안을 세 바퀴나 네 바퀴째 돌고 계실 겁니다. 만약 라이브 리전을 다뤄본 적이 없다면, 가장 엄격한 기술적 용어로 표현하자면 이렇습니다. 우와, 정말 엉망진창이죠.
보조 공학 기술 (Assisted browsing) 환경에서, 사용자의 상호작용에 따라 페이지의 일부가 변경되거나 무언가가 비동기적 (Asynchronously)으로 로드되어 페이지에 추가되는 경우, 사용자가 해당 변경된 콘텐츠로 초점 (Focus)을 옮기기 전까지는 그 변화를 발견할 수 없습니다. 사용자는 무엇이 바뀌었는지는커녕, 무언가가 바뀌었다는 사실조차 알 방법이 없습니다. 라이브 리전 (Live regions)은 적어도 설계상 이러한 문제를 해결합니다. aria-live 속성이 있는 요소는 해당 요소 내에 포함된 마크업 (Markup)의 변경 사항에 대해 음성 안내 (Narration)를 유도합니다. 즉, 마크업이 변경되면 변경된 마크업이 소리 내어 읽힙니다. 만약 aria-live의 값이 assertive라면, 보조 기술에 "이것은 긴급하며 즉시 읽어야 합니다"라고 알립니다. 만약 aria-live의 값이 polite라면, "이것은 다음 자연스러운 기회에 읽어야 합니다"라고 말합니다. 요소에 role="alert" 또는 role="status"를 사용하는 것은 각각 aria-live="assertive" 및 aria-live="polite"와 기능적으로 동일합니다. 서류상으로는 꽤 합리적으로 들리지 않나요?
당연히 우리는 정확히 어떤 정보가 어떻게 읽힐지를 미세 조정 (Fine-tune)할 방법이 필요했고, 그래서 라이브 리전의 동작을 결정하는 몇 가지 다른 속성들이 존재합니다:
aria-atomic
true: 요소 내에서 변경되는 텍스트만 알림false(기본값): 요소 내의 무언가가 변경될 때 라이브 리전의 전체 내용을 읽음
aria-relevant
text: 라이브 리전 내부의 구절 콘텐츠 (Phrasing content) — 이름 그대로 텍스트 — 가 변경될 때 사용자에게 알림additions: 라이브 리전에 노드 (Node)가 추가될 때 사용자에게 알림removals: 라이브 리전에서 노드가 제거될 때 사용자에게 알림all(기본값): 텍스트가 변경되거나, DOM에 요소가 추가 또는 제거될 때 사용자에게 알림
다시 말하지만, 이론적으로는 충분히 탄탄합니다! 문제가 해결된 것 같죠? 하지만 라이브 리전 (live regions)을 무엇인가를 위해 실제로 _사용_하려는 순간 문제가 발생합니다. 실제로 브라우저와 보조 기술 (assistive technologies)은 구현 방식이 매우 일관되지 않으며, 특히 라이브 리전 내의 중첩된 마크업 (nested markup)과 관련된 부분에서 더욱 그렇습니다. aria-live가 예상대로 작동하게 하려면, 원래 사용해야만 하는 의미론적으로 유의미한 마크업들을 모두 제거해야 하는 상황에 자주 직면하게 됩니다. 보조 브라우징 환경에서 안정적으로 작동하려면, 라이브 리전은 낭독이 트리거되는 시점에 이미 DOM에 의미 있게 존재해야 합니다. 라이브 리전을 display: none 상태에서 전환하거나, 낭독될 콘텐츠와 함께 페이지에 주입할 수는 없습니다. 그렇게 하면 콘텐츠가 낭독되지 않는 타이밍 문제(timing issues)가 발생하기 때문입니다. 브라우저가 라이브 리전을 처음
설령 그 모든 것이 사실이 아니라 하더라도, 라이브 리전 (live regions)의 목적과 현대 웹이 구축되는 방식 사이에는 근본적인 불일치가 존재합니다. 앞서 말했듯이, 라이브 리전은 해당 요소에 마크업이 추가되거나 제거될 때만 작동하며, 이는 페이지의 가시적인 콘텐츠 변화를 일으키는 대부분의 상호작용 방식과는 일치하지 않습니다. 이미 문서에 존재하지만 비활성 상태인 마크업을 드러낼 때는 라이브 리전이 아무런 도움이 되지 않습니다. 예를 들어, 가시적인 display 속성과 display: none 사이를 전환하는 경우를 들 수 있습니다. 이러한 사용 사례는 현재 문서의 구조적 변경만큼이나, 혹은 그보다 더 흔하게 발생합니다.
이러한 모든 제한 사항으로 인해, 라이브 리전은 거의 독점적으로 임시방편적인 알림 API (notification APIs)로 사용되어 왔습니다. 즉, 페이지 어딘가에 하나 이상의 aria-live 요소를 숨겨두고(시각적으로는 숨겨져 있지만 접근성 트리 (accessibility tree)에서는 display: none을 통해 제거되지 않은 상태), 낭독되기를 원하는 텍스트를 필요에 따라 업데이트하는 방식입니다. 저 또한 이런 방식을 사용해 본 적이 있는데, 이는 매우 투박합니다. 무엇보다 라이브 리전 자체가 본질적으로 일관성이 없기 때문입니다. 또한 이렇게 주입된 콘텐츠는 보조 기술을 통해 페이지를 탐색하는 사용자에게도 반드시 노출되며, 원래의 의미와 분리된 채 문서 속에 떠돌게 됩니다. 만약 사후 처리를 꼼꼼하게 하지 않는다면, 페이지에 문맥상 관련 없는 낭독을 제공하여 잠재적으로 혼란을 줄 수 있는 요소를 추가하게 되는 셈입니다. 하지만 무엇보다 큰 문제는, 새롭고 보이지 않는 우려 사항을 추가하게 된다는 점입니다. 즉, 전담 테스트와 유지보수가 필요한 기능이자, 말 그대로 눈에 보이지 않는 방식으로 고장 날 수 있으며, 짜증을 유발하거나, 오해를 불러일으키거나, 혼란을 주는 등 그 모든 문제를 일으킬 잠재력이 있는 요소를 추가하는 것입니다.
접근성 (Accessibility) 작업에서 당신을 빠뜨리는 것이 바로 이것입니다. 고립된 상태에서 내린 빠르고 쉬운 결정들이 전체적인 경험의 맥락 속에서 예기치 못한 결과를 초래할 수 있으며, 이러한 가설들을 매우 주의 깊게 — 초기 단계부터 빈번하게 — 테스트하지 않는 한, 그 결과가 무엇이 될지 알 수 없습니다.
ariaNotify 메서드는 이러한 종류의 Rube Goldberg식 접근성 장치(contraption)를 대신하여, 복잡하고 (불안정한) 마크업(markup) 없이도 실제 스크린 리더 알림 API를 제공합니다:
document.ariaNotify( "Hello, World." );
ariaNotify는 Element 인터페이스 또는 Document 인터페이스의 메서드로 사용할 수 있습니다. 여기서 보게 될 예제들 사이에는 두 방식의 유의미한 차이가 없지만, document.ariaNotify()를 호출하는 것은 구체적으로 html 요소에 지정된 lang 속성을 사용하여 알림의 언어를 추론한다는 점을 알아둘 가치가 있습니다:
const btn = document.querySelector( "button.announce" );
btn.addEventListener("click", function( e ) {
...
반면, 요소(element)에서 ariaNotify()를 호출하는 것은 해당 요소의 가장 가까운 조상(ancestor)의 lang 속성을 사용하여 알림의 언어를 결정한다는 것을 의미합니다:
const btn = document.querySelector( "button.announce" );
btn.addEventListener("click", function( e ) {
...
ariaNotify는 명시적인 우선순위 레벨(priority level)을 설정할 수 있는 두 번째 매개변수를 허용합니다:
const btn = document.querySelector( "button.announce" );
btn.addEventListener("click", function( e ){
...
이러한 알림의 기본 우선순위는 priority: "normal"이며, 이는 aria-live="polite" (또는 role="status")와 같이 동작합니다. 명시적으로 priority: "high"를 설정하면 aria-live="assertive" (또는 role="alert")가 그러하듯, 현재의 음성 안내를 우선시하거나 잠재적으로 중단시킬 수 있습니다.
지금까지의 API가 바로 이것 전부입니다. 마크업을 복잡하게 만지거나 타이밍을 미세하게 조정할 필요가 없습니다. 무언가를 음성으로 안내해야 한다면, ariaNotify를 호출하기만 하면 됩니다. 그러면 바로 _음성 안내 (narrated)_가 이루어집니다. 지금 바로 Firefox에서 이를 테스트해 볼 수 있지만, 아직 lang 속성은 반영되지 않은 것으로 보입니다.
CodePen Embed Fallback
JAWS
Polite, button. To activate, press space bar. [spacebar pressed] Space. Hello, World.
Assertive, button. To activate, press space bar. [spacebar pressed] Space. Hello, World.
Educado [정확하게 발음됨], button. To activate, press space bar. Space. Hola, Mundo [부정확하게 발음됨].
NVDA
Polite, button. [spacebar pressed] Hello, World.
Assertive, button. [spacebar pressed] Hello, World.
Educado [정확하게 발음됨], button. [spacebar pressed] Hola, Mundo [부정확하게 발음됨].
VoiceOver
Polite, button. You are currently on a button [spacebar pressed] inside of a frame. To click this button, press Control–Option–Space. To exit this web area, press Control–Option–Shift-Up Arrow. Hello, World.
Assertive, button. You are currently on a button [spacebar pressed] Hello, World.
Educado [정확하게 발음됨], button. You are currently on a button [spacebar pressed] inside of a frame. To click this button, press Control–Option–Space. To exit this web area, press Control–Option–Shift-Up Arrow. Hola, Mundo [부정확하게 발음됨].
꽤 괜찮죠? 우리가 그동안 라이브 영역 (live regions)을 사용하며 겪었던 방식에 비하면 엄청난 개선입니다. 저로서는 ariaNotify를 거의 사용할 뻔했다가 — 다시 한번 — 즉시 마음을 돌리게 될 순간이 기다려지네요!
왜 망설여지는 걸까요? 음, 접근성 (accessibility) 업계에서는 ARIA 사용법을 배우는 데 세 단계가 있다고 말하곤 합니다.
- ARIA를 사용하지 않는다.
- ARIA를 사용한다.
- ARIA를 사용하지 않는다.
W3C는 그들의 전문 분야답게 이를 좀 더 공식적인 용어로 표현합니다:
만약 당신이 요구하는 의미론(semantics)과 동작(behavior)이 이미 내장된 네이티브 HTML 요소 [HTML] 또는 속성(attribute)을 사용할 수 있다면, 요소를 재사용하여 접근성을 높이기 위해 ARIA 역할(role), 상태(state) 또는 속성(property)을 추가하는 대신, 그렇게 하십시오.
ARIA 숙련도의 두 번째 단계가 바로 우리가 문제에 빠지게 되는 지점입니다. 웹은 혼란스러운 곳이지만, 보조 기술(assistive technologies)은 웹과 함께 발전해 왔으며, 사용자가 마주칠 수 있는 몇 가지 흔한 문제들을 보완하는 법을 학습해 왔습니다. 예를 들어, 클릭했을 때 뒤따르는 시각적으로 숨겨진 콘텐츠를 드러내는 h2 요소가 있다고 가정해 봅시다. 해당 요소는 시각적으로는 그 상호작용이 명확하게 보이도록 제시될 수 있지만, 우리의 개입이 없다면 스크린 리더(screen reader)를 통해 브라우징하는 사용자에게는 별도로 신호가 전달되지 않을 수 있습니다. 이를 해결하기 위해, 보조 기술은 해당 헤딩(heading) 요소에 click 이벤트 리스너(event listener)가 바인딩(bound)되어 있는 것을 발견하고 사용자의 포커스(focus)를 받았을 때, 해당 요소를 "클릭 가능함"이라고 유용하게 읽어줄 수 있습니다. 물론 이것이 사용자에게 이 요소의 목적을 명시적으로 알리는 것만큼 좋지는 않지만, 우리가 그 상호작용을 명시적으로 만들지 않았더라도 "작동은 하는" 방식입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 CSS-Tricks의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기