ARIA(Accessible Rich Internet Applications)는 개발자들에게 동적이고 복잡한 웹 인터페이스를 스크린 리더 사용자도 접근할 수 있도록 만드는 강력한 도구 모음을 제공하지만, 잘못된 사용이 만연하고 그 피해도 큽니다. 이 가이드는 모든 주요 ARIA 역할(category)을 하나씩 분석하고, ARIA 사용의 황금 규칙을 설명하며, 올바르게 적용할 수 있도록 구체적인 코드 예제를 보여줍니다.
다음은 정신이 번쩍 드는 숫자다. WebAIM이 상위 백만 개 웹사이트의 홈페이지를 분석한 결과, ARIA 속성이 포함된 페이지는 ARIA가 전혀 없는 페이지보다 평균적으로 훨씬 더 많은, 감지 가능한 접근성 오류를 포함하고 있었다. 이는 ARIA 사용에 반대하는 주장이 아니라, ARIA를 올바르게 사용해야 한다는 주장이다. ARIA는 현대 접근성 도구 상자에서 가장 강력한 도구 중 하나지만, 동시에 가장 오해받는 도구이기도 하다. 제대로 사용하면 장애가 있는 수백만 명의 사용자에게 사이트를 의미 있게 개방할 수 있다. 잘못 사용하면 그들의 경험을 적극적으로 더 나쁘게 만든다.
ARIA란 무엇이며 왜 존재하는가?
ARIA는 Accessible Rich Internet Applications의 약자다. 이는 W3C의 Web Accessibility Initiative가 정의한 HTML 속성 집합으로, 개발자가 스크린 리더, 점자 디스플레이, 음성 제어 소프트웨어와 같은 보조 기술에 의미 정보를 전달할 수 있게 해준다. 브라우저가 페이지를 렌더링할 때, DOM(눈에 보이는 것)과 Accessibility Tree(보조 기술이 읽는 것)라는 두 개의 병렬 구조를 만든다. ARIA 속성은 이 Accessibility Tree를 수정하여 커스텀 컴포넌트가 무엇이며 어떻게 동작하는지를 정확하게 설명할 수 있게 해준다.
ARIA의 필요성은 실제 문제에서 비롯되었다. HTML은 애플리케이션이 아니라 문서를 위해 설계되었다. 웹이 탭 인터페이스, 모달 다이얼로그, 드래그 앤 드롭, 실시간 데이터 피드 같은 풍부하고 상호작용적인 경험을 위한 플랫폼으로 진화했을 때, 네이티브 HTML 요소만으로는 스크린 리더에 그 컴포넌트가 무엇이며 어떻게 동작하는지 전달할 수 없었다. ARIA는 그 간극을 메운다. MDN의 표현을 빌리면, ARIA는 “애플리케이션에서 일반적으로 사용되는 상호작용과 위젯을, 다른 메커니즘이 없을 때 보조 기술에 전달할 수 있도록 HTML을 보완한다.”
ARIA는 시각적 표현을 바꾸지 않는다. 동작을 추가하지도 않는다. 키보드 지원을 자동으로 제공하지도 않는다. 오로지 Accessibility Tree가 보조 기술에 노출하는 내용을 수정할 뿐이다. 이것은 중요한 구분이며, 개발자가 ARIA를 지름길처럼 사용하려 할 때 발생하는 많은 오류의 원인이 된다.
이 명세는 W3C가 WAI-ARIA라는 이름으로 관리하고 있으며, 현재 버전은 1.2이고 1.3이 활발히 개발 중이다. 이 명세는 역할(roles), 상태(states), 속성(properties)으로 이루어진 온톨로지를 제공하며, 이를 통해 현대 웹 패턴 전반에 걸친 접근 가능한 사용자 인터페이스 요소를 설명한다.
세 가지 기둥: Roles, States, Properties
ARIA 코드를 한 줄이라도 작성하기 전에, 명세가 제공하는 세 가지 서로 다른 구성 요소를 이해해야 한다. 이들은 서로 대체 가능하지 않으며, 이들을 혼동하는 것이 가장 흔한 오류 원인 중 하나다.
Roles는 요소가 무엇인지(is)를 정의한다. 역할은 “지금 내가 보고 있는 것이 어떤 종류의 것인가?”라는 질문에 답한다. 예로는 button, dialog, navigation, tablist, progressbar 등이 있다. 역할은 role 속성으로 적용한다: <div role='button'>. 역할은 요소의 목적을 보조 기술에 전달하여 사용자가 어떻게 상호작용해야 하는지 알 수 있게 한다.
States는 요소의 동적인 상태, 즉 사용자가 페이지와 상호작용하면서 변하는 조건을 설명한다. aria-expanded 속성은 스크린 리더에게 접을 수 있는 섹션이 열려 있는지 닫혀 있는지를 알려준다. aria-checked는 커스텀 체크박스가 체크되어 있는지를 반영한다. 상태는 JavaScript와 동기화되어야 한다. 한 번도 바뀌지 않는 정적인 aria-expanded='false'는 쓸모없을 뿐 아니라 적극적으로 오해를 불러일으킨다.
Properties는 요소에 대한 설명적이고, 보통은 더 안정적인 정보를 제공한다. aria-label은 요소에 눈에 보이는 텍스트를 덮어쓰는 접근 가능한 이름을 부여한다. aria-labelledby는 레이블 역할을 하는 텍스트를 가진 다른 요소를 가리킨다. aria-describedby는 추가 설명 텍스트를 연결한다. aria-required는 폼 필드를 반드시 채워야 함을 나타낸다. 상태는 자주 바뀌는 것이 기대되는 반면, 속성은 한 번 설정하면 그대로 두는 경우가 많다(물론 예외도 있다).
Roles는 요소가 무엇인지 정의한다. States는 지금 어떻게 동작하고 있는지를 정의한다. Properties는 추가적인 설명 컨텍스트를 제공한다. 완전히 접근 가능한 커스텀 컴포넌트를 만들려면 이 세 가지가 모두 함께 작동해야 한다.
황금률 — 그리고 생각보다 더 중요한 이유
W3C의 ARIA 사용 첫 번째 규칙은 명확하다. 필요한 의미와 동작이 이미 내장된 네이티브 HTML 요소나 속성을 사용할 수 있다면, 그것을 사용하라. 처음부터 ARIA에 손대지 말라. 이는 종종 “나쁜 ARIA보다는 ARIA가 없는 것이 낫다(no ARIA is better than bad ARIA)” 원칙이라고 불리는데, 이는 선의이지만 잘못된 ARIA 사용이 초래하는 매우 현실적인 위험을 반영한 표현이다.
네이티브 HTML 요소는 암묵적인 ARIA 의미를 무료로 제공한다. <button> 요소는 이미 Accessibility Tree에 버튼으로 노출된다. 이미 키보드 포커스를 받을 수 있다. 이미 Enter와 Space 키 모두에 반응한다. 이미 레이블을 읽어준다. <div role='button'>을 작성하는 순간, 키보드 처리, 포커스 관리, 상태 업데이트 등 모든 동작을 JavaScript로 수동 구현해야 하는 책임을 떠안게 된다. 이는 이론적인 문제가 아니다. 커스텀 버튼에서 키보드 지원을 빠뜨리는 것은 실제 서비스에서 가장 흔하고 가장 해로운 ARIA 오류 중 하나다.
ARIA가 진정으로 필요해지는 경우는 몇 가지 시나리오에 집중되는 경향이 있다. HTML에 해당 요소가 없는 복잡한 위젯(캐러셀, 자동완성 기능이 있는 콤보박스, 트리 뷰)을 만들 때, DOM 구조를 재구성하는 비용이 너무 커서 레거시 마크업을 보정해야 할 때, 커스텀 의미를 노출해야 하는 웹 컴포넌트를 만들 때, 또는 네이티브 요소에 대한 브라우저와 보조 기술 지원이 너무 일관성이 없어서 ARIA 동등 요소가 실제로 더 안정적으로 동작할 때 등이다.
이러한 시나리오 밖에서는, 항상 먼저 떠올려야 할 것은 시맨틱 HTML이다. <div role='navigation'> 대신 <nav>를 사용하라. <div role='main'> 대신 <main>을 사용하라. <div role='button'> 대신 <button>을 사용하라. 네이티브 요소가 더 견고하고, 지원도 더 좋으며, 유지보수도 훨씬 덜 필요하다.
주요 ARIA 역할 카테고리 둘러보기
WAI-ARIA 명세는 역할을 여러 카테고리로 조직한다. 이 카테고리를 이해하면 언제 어떤 역할을 사용해야 할지 판단하는 데 도움이 된다.
Landmark Roles
Landmark 역할은 페이지의 주요 영역을 표시하여, 스크린 리더 사용자가 키보드 단축키로 핵심 섹션으로 바로 이동할 수 있게 한다. 가장 흔히 사용되는 Landmark 역할은 banner, navigation, main, complementary, contentinfo, search, form이다. 이들 각각은 <header>, <nav>, <main>, <aside>, <footer> 등과 같은 네이티브 HTML 요소에 직접 대응한다. 실제로는, 현대 시맨틱 HTML을 사용하고 있다면 Landmark 역할은 거의 항상 중복된다. 구조적인 이유로 비시맨틱 마크업을 사용할 수밖에 없을 때만 추가하라.
<!-- Prefer this -->
<header>
<nav>...</nav>
</header>
<main>...</main>
<footer>...</footer>
<!-- Use ARIA only when you must use divs -->
<div role='banner'>
<div role='navigation'>...</div>
</div>
<div role='main'>...</div>
<div role='contentinfo'>...</div>
Widget Roles
Widget 역할은 사용자가 직접 조작하는 인터랙티브 컴포넌트를 설명한다. 많은 위젯 패턴에 네이티브 HTML 동등 요소가 없기 때문에, 이 영역이 ARIA가 가장 중요한 역할을 하는 곳이다. 일반적인 Widget 역할에는 button, checkbox, dialog, menu, menuitem, slider, tablist, tab, tabpanel, tooltip, tree, combobox 등이 있다.
Widget 역할을 사용하면 키보드 상호작용에 대한 모든 책임을 떠안게 된다. WAI-ARIA Authoring Practices Guide(APG)는 각 위젯 유형에 대해 기대되는 키보드 패턴을 정의한다. 예를 들어, 탭 간 이동을 위한 방향키, 다이얼로그를 닫기 위한 Escape, 리스트박스에서 첫 번째와 마지막 항목으로 이동하기 위한 Home과 End 등이다. 이러한 패턴을 구현하지 않으면, 컴포넌트는 기술적으로는 레이블이 붙어 있지만 키보드만 사용하는 사용자에게는 사실상 사용할 수 없게 된다.
<!-- A custom tab interface -->
<div role='tablist' aria-label='Account settings'>
<button role='tab' aria-selected='true' aria-controls='panel-profile' id='tab-profile'>
Profile
</button>
<button role='tab' aria-selected='false' aria-controls='panel-security' id='tab-security' tabindex='-1'>
Security
</button>
</div>
<div role='tabpanel' id='panel-profile' aria-labelledby='tab-profile'>
<p>Profile settings content</p>
</div>
<div role='tabpanel' id='panel-security' aria-labelledby='tab-security' hidden>
<p>Security settings content</p>
</div>
Live Region Roles
Live region은 ARIA의 가장 진정으로 유용한 기능 중 하나다. 이는 상태 메시지, 오류 알림, 채팅 메시지, 로딩 인디케이터와 같은 동적 콘텐츠 업데이트를 화면 변화를 볼 수 없는 사용자에게 보조 기술이 읽어줄 수 있게 한다. Live region이 없다면, 폼을 제출한 스크린 리더 사용자는 포커스가 명시적으로 결과로 이동하지 않는 한 성공했는지 실패했는지 전혀 알 수 없을 수 있다.
핵심 Live region 역할은 alert, status, log, marquee, timer다. alert 역할은 암묵적으로 aria-live='assertive' 설정을 가지며, 이는 사용자를 즉시 방해한다는 뜻이다. 오류나 긴급 경고에 적합하다. status 역할은 aria-live='polite'를 사용하여, 사용자가 현재 작업을 마칠 때까지 기다렸다가 알린다. 성공 메시지나 진행 상태 표시 등에 이상적이다.
<!-- Polite status message for non-urgent feedback -->
<div role='status' aria-live='polite' aria-atomic='true'>
<!-- Dynamically inject text here with JavaScript -->
</div>
<!-- Assertive alert for errors that demand immediate attention -->
<div role='alert'>
Please correct the errors below before submitting.
</div>
Live region의 핵심은, 동적 콘텐츠가 삽입되기 전에 컨테이너가 DOM에 존재해야 한다는 점이다. Live region을 생성하는 동시에 내용을 채우면 스크린 리더가 이를 놓치는 경우가 많다. 페이지 로드 시 컨테이너를 만들어 두고, 이벤트가 발생할 때 JavaScript로 내용을 채워 넣어라.
Document Structure Roles
article, list, listitem, table, row, cell, figure, heading과 같은 Document structure 역할은 콘텐츠의 구조적 구성을 설명한다. 이들 대부분은 이제 네이티브 HTML 요소로 대체되었으며, MDN은 대부분의 Document structure 역할에 대해 “이제 브라우저가 동일한 의미를 가진 시맨틱 HTML 요소를 지원하므로 더 이상 사용하지 않아야 한다”고 명시한다. 주요 예외는 네이티브 HTML 요소를 사용할 수 없는 커스텀 렌더링 환경, 웹 컴포넌트, SVG 기반 콘텐츠를 다룰 때다.
모든 개발자가 알아야 할 필수 ARIA 속성
역할 외에도, 실제 접근성 작업에서 끊임없이 등장하는 ARIA 속성이 몇 가지 있다. 가장 자주 사용하게 될 것들은 다음과 같다.
- aria-label: 눈에 보이는 텍스트 레이블이 없거나, 보이는 텍스트만으로는 충분하지 않을 때 요소에 접근 가능한 이름을 제공한다. 일반적인 사용 사례: 아이콘만 있는 버튼, 눈에 보이는 레이블이 없는 검색 필드, 모달의 닫기 버튼.
aria-label은 눈에 보이는 텍스트나 네이티브 레이블을 덮어쓰므로, 눈에 보이는 텍스트가 있는 요소에 사용할 때는 주의해야 한다. - aria-labelledby: 접근 가능한 이름 역할을 하는 텍스트 콘텐츠를 가진 하나 이상의 요소를 가리킨다. 레이블 텍스트가 눈에 보이는 콘텐츠와 동기화된 상태로 유지되기 때문에, 복잡한 경우에는
aria-label보다 더 견고하다. 공백으로 구분된 요소 ID 목록을 받으며, 보조 기술은 참조된 텍스트를 순서대로 이어서 읽는다. - aria-describedby: 이름이 아니라 추가적인 컨텍스트를 제공하는 설명 텍스트를 연결한다. 폼 필드를 오류 메시지와 연결하거나, 툴팁을 설명 대상 요소와 연결하는 데 사용하라. 스크린 리더는 일반적으로 요소의 이름과 역할을 읽은 뒤에 이를 읽어준다.
- aria-hidden: 요소를 Accessibility Tree에서 완전히 제거한다. 장식용 아이콘, 중복 콘텐츠, 스크린 리더 사용자에게 잡음을 유발할 시각적 요소에 매우 유용하다. 포커스를 받을 수 있는 요소에는 절대
aria-hidden='true'를 적용하지 말라. 사용자는 여전히 탭으로 이동할 수 있지만, 그에 대한 정보를 전혀 받지 못하게 된다. - aria-expanded: 드롭다운, 아코디언, 디스클로저 위젯과 같은 접을 수 있는 요소가 현재 열려 있는지 닫혀 있는지를 전달한다. JavaScript로 동적으로 토글해야 한다. 정적인 값은 이 속성을 아예 생략하는 것보다 더 나쁘다.
- aria-current: 집합 내 현재 항목을 나타내며, 가장 흔하게는 내비게이션에서 활성 페이지 링크(
aria-current='page')나 다단계 프로세스의 현재 단계를 표시하는 데 사용된다.
실제로 접근성을 해치는 흔한 ARIA 실수
ARIA가 있는 페이지가 없는 페이지보다 더 많은 접근성 오류를 보이는 만큼, 무엇이 가장 자주 잘못되는지 명확히 짚고 넘어갈 가치가 있다. 이는 예외적인 경우가 아니라, 실제 서비스 코드에서 매일 발견되는 패턴이다.
강한 네이티브 의미를 가진 요소에 ARIA 역할을 사용하는 경우. 일부 HTML 요소는 명세에서 “강한 네이티브 의미(strong native semantics)”를 가진다고 하는데, 이는 브라우저에 깊이 내장되어 안전하게 덮어쓸 수 없는 의미를 뜻한다. <button>이나 <input>에 부적절한 역할을 지정하면 브라우저가 ARIA 역할을 완전히 무시하거나, 보조 기술을 혼란스럽게 하는 모순된 동작을 만들어낼 수 있다. 선언한 역할은 해당 요소에 적절해야 한다.
Widget 역할에서 키보드 지원을 잊는 경우. ARIA의 role='button'은 스크린 리더에게 그 요소가 버튼이라고 알려줄 뿐이다. 요소를 키보드로 조작 가능하게 만들지는 않는다. <div>에 role='button'을 사용한다면, 포커스를 받을 수 있도록 tabindex='0'를 추가해야 하고, Enter와 Space 키 모두에 대한 이벤트 리스너를 추가해야 한다. 이 중 하나라도 빠지면 키보드만 사용하는 사용자의 경험은 깨진다.
<!-- Incomplete and inaccessible -->
<div role='button' onclick='doSomething()'>Submit</div>
<!-- Correct custom button implementation -->
<div
role='button'
tabindex='0'
onclick='doSomething()'
onkeydown='if(event.key==="Enter"||event.key===" ")doSomething()'
>Submit</div>
<!-- Or, the right answer: just use a button -->
<button onclick='doSomething()'>Submit</button>
포커스를 받을 수 있는 요소에 aria-hidden을 사용하는 경우. aria-hidden='true'를 포커스를 받을 수 있는 요소에 적용하면, Accessibility Tree에서는 숨기지만 키보드 내비게이션에서는 숨기지 않는다. 키보드 사용자는 여전히 탭으로 이동할 수 있지만, 그에 대한 정보를 전혀 받지 못하고 무엇을 하는 요소인지 알 수 없다. 이는 WCAG 2.1의 성공 기준 4.1.2(Name, Role, Value) 위반이다.
갱신되지 않는 ARIA 상태. UI가 변경될 때 aria-expanded, aria-checked, aria-selected와 같은 상태를 업데이트하지 않으면, 스크린 리더 사용자는 인터페이스에 대해 근본적으로 잘못된 정보를 받게 된다. 메뉴가 시각적으로는 열렸지만 트리거가 여전히 aria-expanded='false'로 읽힌다면, 이는 적극적으로 기만적이다.
중복된 역할. <nav> 요소에 role='navigation'을 추가하거나, <button>에 role='button'을 추가하는 것은 아무런 유용한 일을 하지 않는다. 코드를 어지럽힐 뿐 아니라, 특정 보조 기술 조합에서는 혼란을 줄 수도 있다. 네이티브 의미를 신뢰하라.
ARIA와 WCAG: 그 관계 이해하기
ARIA는 WCAG가 아니다. 이 둘은 함께 작동하는 별도의 명세다. WCAG(Web Content Accessibility Guidelines)는 접근 가능한 콘텐츠를 위해 요구되는 결과, 즉 무엇을 정의한다. ARIA는 그 결과 중 일부를 달성하기 위한 기술적 메커니즘, 즉 어떻게의 일부다. ARIA와 가장 관련이 깊은 WCAG 성공 기준은 4.1.2: Name, Role, Value로, 모든 사용자 인터페이스 컴포넌트가 이름을 가지고, 그 역할을 보조 기술에 노출하며, 상태와 속성을 프로그래밍 방식으로 전달해야 한다고 요구한다. ARIA는 커스텀 컴포넌트에 대해 이 기준을 충족하는 주요 도구 중 하나다.
ARIA는 다른 여러 성공 기준도 지원한다. Landmark 역할은 건너뛰기 내비게이션을 가능하게 함으로써 2.4.1(Bypass Blocks)에 기여한다. Live region은 포커스를 받지 않고도 상태 메시지가 프로그래밍 방식으로 판별 가능해야 한다는 WCAG 2.1의 4.1.3(Status Messages)을 충족하는 데 적절한 도구인 경우가 많다. aria-label과 aria-labelledby를 적절히 사용하면 2.4.6(Headings and Labels)과 1.3.1(Info and Relationships)을 충족하는 데 도움이 된다.
WCAG 준수는 점점 더 법적 요구사항이 되고 있다는 점도 주목할 만하다. European Accessibility Act는 2025년 6월에 전면 발효되어, EU 회원국 전역의 광범위한 민간 디지털 서비스에 의무적인 접근성 요구사항을 확장했다. 미국에서는 ADA가 웹 접근성을 포함하는 것으로 계속 해석되고 있으며, Section 508에 따른 연방 요구사항은 정부 및 연방 자금을 받는 기관에 적용된다. ARIA를 올바르게 이해하는 것은 모범 사례일 뿐 아니라, 점점 더 준수 의무의 일부가 되고 있다.
ARIA 구현 테스트하기
ARIA 구현이 실제로 동작하는지 확인하는 유일한 방법은 실제 보조 기술로 테스트하는 것이다. axe, WAVE, Lighthouse와 같은 자동화 도구는 필수 속성 누락, 잘못된 역할, 포커스를 받을 수 있는 요소에 적용된 aria-hidden과 같은 구조적 위반을 잡아낼 수 있다. 하지만 모달을 스크린 리더가 이해할 수 있는 방식으로 읽어주는지, 커스텀 트리 위젯에서 키보드 내비게이션이 기대되는 패턴을 따르는지 등은 알려줄 수 없다.
수동 테스트를 위해 다뤄야 할 주요 스크린 리더는 Windows의 JAWS와 NVDA(함께 데스크톱 스크린 리더 사용의 대다수를 차지한다), 그리고 macOS와 iOS의 VoiceOver다. Android는 TalkBack이 담당한다. 각 스크린 리더와 브라우저 조합은 서로 다르게 동작할 수 있으므로, 최소 두 가지 조합에서 테스트하는 것이 강력히 권장된다. 모든 인터랙티브 상태를 테스트하라. 다이얼로그를 열고, 아코디언을 펼치고, 옵션을 선택하고, 알림을 트리거하라. 시각 사용자가 같은 인터페이스를 보고 이해하는 것과 동일한 내용을 스크린 리더가 알리는지 확인하라.
커스텀 위젯을 테스트할 때는, 해당 위젯 유형에 대해 WAI-ARIA Authoring Practices Guide에 정의된 키보드 상호작용 모델을 따라가 보라. 탭 리스트가 방향키에 반응하지 않거나, 다이얼로그가 포커스를 가두지 못한다면, ARIA 마크업이 자동 검사에서 아무리 올바르게 보이더라도 그것은 실패다.
핵심 요약
- 항상 ARIA보다 시맨틱 HTML을 우선하라.
<button>,<nav>,<main>,<dialog>와 같은 네이티브 요소는 ARIA를 입힌 div보다 훨씬 더 견고하고 코드도 적게 드는 내장 접근성 의미를 제공한다. 네이티브 HTML이 진짜로 부족할 때만 ARIA를 사용하라. - ARIA 역할은 지름길이 아니라 약속이다. 커스텀 요소에
role='button'이나role='dialog'를 적용하는 것은 해당 위젯 유형에 대한 전체 키보드 상호작용 모델을 구현하겠다는 약속이다. 역할에 맞는 동작이 없는 컴포넌트는 혼란을 만들고 WCAG 위반을 초래한다. - ARIA 상태를 UI와 동기화하라.
aria-expanded,aria-checked,aria-selected,aria-live콘텐츠와 같은 동적 속성은 UI가 변경될 때마다 JavaScript로 업데이트해야 한다. 갱신되지 않은 상태는 적극적으로 해롭다. 사용자에게 잘못된 정보를 전달하기 때문이다. - 동적 콘텐츠 업데이트에는 Live region을 사용하라. 페이지 리로드 없이 업데이트되는 모든 콘텐츠(알림, 오류 메시지, 로딩 상태, 채팅 피드)는
aria-live영역이나alert,status와 같은 적절한 역할이 필요하다. 그래야 스크린 리더 사용자가 시각 사용자와 동일한 정보를 자동으로 받을 수 있다. - 자동화 도구만이 아니라 실제 보조 기술로 테스트하라. 자동 스캐너는 구조적인 ARIA 오류를 잡아낼 수 있지만, 구현이 일관되고 사용 가능한 경험을 제공하는지 검증할 수는 없다. JAWS, NVDA, VoiceOver로 수동 테스트하는 것만이 그 격차를 메우는 유일한 방법이다.
