우리가 무심코 외면하고 있는 15%의 사용자들
프론트엔드 개발을 시작한 지 1년이 된 한 개발자의 고백이 있습니다.
자동완성에서 aria로 시작하는 옵션이 항상 먼저 떴지만, 큰 의문 없이 무시하고 지냈습니다.
이 솔직한 고백은 대부분의 개발자가 겪는 현실을 보여줍니다.
전 세계 인구의 약 15~16%에 해당하는 10억 명 이상의 사람들이 어떤 형태로든 장애를 가지고 있습니다. 많은 사람들이 스크린 리더, 음성 제어, 키보드 탐색 또는 기타 보조 기술에 의존해 웹을 사용합니다. 겉보기에 멋진 웹사이트라도 적절한 ARIA 구현이 없다면, 이 사용자들에게는 완전히 사용할 수 없는 사이트가 됩니다.
ARIA가 해결하는 근본적인 문제
ARIA는 접근 가능한 리치 인터넷 애플리케이션(Accessible Rich Internet Applications)을 의미합니다. ARIA의 핵심은 스크린 리더와 같은 보조 기술과 소통할 수 있는 방법을 제공하는 것입니다.
예를 들어, div와 CSS를 사용해 아름다운 탭 인터페이스를 만들었다고 가정해봅시다. 시각적으로는 완벽하지만, 스크린 리더를 사용하는 사람은 그것이 탭이라는 사실을 전혀 알 수 없습니다. 적절한 ARIA 속성을 추가하면, 스크린 리더가 전체 상호작용 패턴을 즉시 이해하게 됩니다.
접근성이 보장된 웹사이트는 단순히 규정을 준수하는 것 이상의 가치를 지닙니다. 의미론적으로 더 명확하고, 키보드로 탐색하기 쉬우며, 구조가 더 잘 잡혀 있고, 검색 엔진에서도 더 좋은 성과를 냅니다.
시작하기 전 반드시 알아야 할 황금률
의미론적 HTML이 항상 우선입니다. <button> 요소가 존재할 때 <div role="button">을 사용해서는 안 됩니다. HTML이 이미 그 역할을 수행하고 있다면 ARIA로 불필요한 작업을 반복하지 마십시오. 기본 요소는 접근성, 키보드 지원, 예상되는 동작이 내장되어 있습니다. ARIA는 HTML이 부족한 부분을 채우기 위한 것입니다.
역할(Role) 속성: 요소의 정체성을 알려주다
역할 속성은 보조 기술에 해당 요소가 정확히 어떤 유형인지 알려줍니다.
랜드마크 역할: 페이지의 주요 섹션 정의
role="banner"는 메인 헤더 영역을 나타냅니다. 사이트의 로고와 주요 네비게이션이 위치하는 최상단 영역에 사용됩니다. role="navigation"은 모든 네비게이션 메뉴에 적용되며, 사용자가 사이트 내에서 이동할 수 있는 링크 모음을 의미합니다.
role="main"은 페이지의 주요 콘텐츠를 표시하고, role="complementary"는 사이드바 같은 보조 콘텐츠에 사용됩니다. role="contentinfo"는 푸터 콘텐츠를, role="search"는 검색 영역을, role="form"은 양식 영역을, role="region"은 식별할 가치가 있는 중요한 섹션을 나타냅니다.
문서 구조 역할: 콘텐츠의 조직화
role="article"은 블로그 포스트나 뉴스 기사처럼 독립적인 구성 요소를 나타냅니다. role="feed"는 소셜 미디어 피드처럼 스크롤 가능한 기사 목록에 사용되며, role="figure"는 이미지, 다이어그램, 코드 스니펫에 적용됩니다.
테이블 관련 역할로는 role="table", role="row", role="cell", role="columnheader", role="rowheader" 등이 있으며, 이들은 표 형식 데이터의 구조를 명확히 전달합니다. role="list"와 role="listitem"은 항목 목록을 나타내고, role="separator"는 시각적 구분선을, role="toolbar"는 컨트롤이 있는 툴바를, role="tooltip"은 컨텍스트 팝업 정보를 표현합니다.
위젯 역할: 모든 인터랙티브 컴포넌트
인터랙티브 요소를 위한 역할이 가장 다양합니다. role="button", role="checkbox", role="radio", role="textbox", role="searchbox" 등 기본적인 폼 요소부터 시작합니다.
role="combobox"는 입력과 드롭다운이 결합된 형태이고, role="listbox"와 role="option"은 옵션 목록을 구성합니다. 메뉴 시스템을 위해서는 role="menu", role="menubar", role="menuitem", role="menuitemcheckbox", role="menuitemradio"가 제공됩니다.
탭 인터페이스는 role="tab", role="tablist", role="tabpanel"로 구성되며, 트리 구조는 role="tree", role="treeitem", role="treegrid"로 표현됩니다. role="progressbar"는 진행률 표시기에, role="slider"는 범위 슬라이더에 사용됩니다.
라이브 영역 역할: 동적 콘텐츠 알림
실시간으로 변경되는 콘텐츠를 위한 역할도 중요합니다. role="alert"는 긴급 메시지를, role="log"는 추가되는 로그를, role="status"는 상태 메시지를, role="timer"는 타이머 및 카운트다운을 나타냅니다.
Window 역할: 모달 상호작용
role="dialog"는 일반 대화 상자를, role="alertdialog"는 경고 대화 상자를 나타냅니다. 이들은 모달 팝업이나 확인 대화 상자를 구현할 때 필수적입니다.
상태(State)와 속성(Property): 요소의 현재 상태 전달
상호작용 상태 관리
aria-checked는 체크박스나 라디오 버튼의 선택 상태를 true, false, mixed 값으로 표현합니다. aria-disabled는 비활성화 상태를, aria-expanded는 확장 가능한 요소의 펼침/접힘 상태를 나타냅니다.
aria-pressed는 토글 버튼의 눌림 상태를, aria-selected는 옵션의 선택 상태를 표시합니다. aria-invalid는 유효성 검사 상태를 true, false, grammar, spelling 값으로 전달하며, aria-errormessage로 오류 메시지와 연결할 수 있습니다.
레이블링과 설명
aria-label은 요소에 직접 접근 가능한 레이블을 제공합니다. 시각적으로는 보이지 않지만 스크린 리더가 읽어주는 텍스트를 지정할 수 있습니다. aria-labelledby는 다른 요소의 ID를 참조해 레이블로 사용하고, aria-describedby는 추가 설명을 제공하는 요소를 연결합니다.
값 관련 속성
슬라이더나 프로그레스 바 같은 범위를 가진 요소에는 aria-valuemin, aria-valuemax, aria-valuenow를 사용해 최솟값, 최댓값, 현재값을 지정합니다. aria-valuetext는 숫자 값을 사람이 읽기 쉬운 형태로 변환해 제공합니다.
기타 유용한 속성
aria-hidden은 보조 기술에서 요소를 숨기거나 표시합니다. 장식적인 요소를 스크린 리더에서 제외할 때 유용합니다. aria-modal은 모달 대화 상자를 나타내고, aria-required는 필수 입력 필드를 표시합니다.
aria-placeholder는 플레이스홀더 힌트를, aria-readonly는 읽기 전용 상태를, aria-orientation은 수평/수직 방향을 지정합니다.
라이브 영역(Live Region) 속성: 변경 사항 즉시 알리기
알림 긴급도 제어
aria-live 속성은 동적 콘텐츠가 변경될 때 사용자에게 알리는 방식을 제어합니다. aria-live="off"는 알림을 하지 않고, aria-live="polite"는 사용자가 유휴 상태일 때 알리며, aria-live="assertive"는 즉시 중단하고 알립니다.
예를 들어, 검색 결과 카운트가 실시간으로 업데이트되는 경우 aria-live="polite"를 사용해 사용자의 작업을 방해하지 않으면서도 변경 사항을 전달할 수 있습니다.
세밀한 알림 제어
aria-atomic은 전체 영역을 다시 읽을지, 변경된 부분만 읽을지 결정합니다. aria-busy는 로딩 중임을 나타내며, aria-relevant는 어떤 유형의 변경 사항을 알릴지 지정합니다(additions, removals, text, all).
관계(Relationship) 속성: 요소 간 연결
aria-controls는 어떤 요소가 다른 요소를 제어하는지 나타냅니다. 예를 들어, 버튼이 패널을 열고 닫는다면 버튼에 aria-controls="panel-id"를 추가합니다.
aria-owns는 DOM 구조상 자식이 아니지만 논리적으로는 자식인 요소를 지정하고, aria-activedescendant는 복합 위젯에서 현재 활성화된 자식 요소를 나타냅니다.
테이블 관련 속성으로는 aria-colcount, aria-colindex, aria-colspan, aria-rowcount, aria-rowindex, aria-rowspan이 있어 복잡한 테이블 구조를 표현할 수 있습니다.
실전 예제: 코드로 보는 ARIA 활용
토글 버튼 구현
<div
role="button"
tabindex="0"
aria-pressed="false"
onclick="this.setAttribute('aria-pressed', this.getAttribute('aria-pressed') === 'false')">
Dark Mode
</div>
이 예제는 다크 모드 토글을 보여줍니다. role="button"으로 역할을 지정하고, tabindex="0"으로 키보드 접근을 가능하게 하며, aria-pressed로 토글 상태를 전달합니다. 단, 실제로는 <button> 요소를 사용하는 것이 더 좋습니다.
모달 대화 상자
<div
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
aria-describedby="dialog-desc">
<h2 id="dialog-title">Delete Item?</h2>
<p id="dialog-desc">This action cannot be undone.</p>
<button>Delete</button>
<button>Cancel</button>
</div>
모달 구현에서는 role="dialog"와 aria-modal="true"로 모달임을 명시하고, aria-labelledby로 제목을, aria-describedby로 설명을 연결합니다.
폼 유효성 검사
<label for="email">Email Address</label>
<input
type="email"
id="email"
aria-required="true"
aria-invalid="true"
aria-describedby="email-error"/>
<span id="email-error" role="alert">
Please enter a valid email address
</span>
폼 검증에서는 aria-required로 필수 필드를 표시하고, aria-invalid로 유효성 상태를, aria-describedby로 오류 메시지를 연결합니다. 오류 메시지에는 role="alert"를 추가해 즉시 알립니다.
확장 가능한 섹션
<button aria-expanded="false" aria-controls="content-panel">
Show More Details
</button>
<div id="content-panel" hidden>
Additional content here...
</div>
아코디언이나 접을 수 있는 섹션에서는 aria-expanded로 펼침/접힘 상태를, aria-controls로 제어하는 패널을 연결합니다.
라이브 검색 결과
<label for="search">Search Products</label>
<input
id="search"
type="search"
aria-controls="results"
aria-describedby="results-count"/>
<div id="results" role="region" aria-live="polite">
<span id="results-count">12 products found</span>
<!-- 검색 결과 목록 -->
</div>
실시간 검색에서는 aria-live="polite"로 결과 변경을 알리고, aria-controls로 검색 입력과 결과 영역을 연결합니다.
절대 하지 말아야 할 실수들
의미론적 HTML을 대체하지 마세요
<!-- ❌ 잘못된 예 -->
<div role="button" onclick="submit()">Submit</div>
<!-- ✅ 올바른 예 -->
<button onclick="submit()">Submit</button>
네이티브 HTML 요소는 접근성, 키보드 지원, 예상 동작이 내장되어 있습니다. ARIA로 이를 재현하려 하지 마세요.
상태를 동기화하세요
<!-- ❌ 상태가 업데이트되지 않음 -->
<button aria-expanded="false" onclick="toggle()">Menu</button>
<!-- ✅ 상호작용으로 상태 업데이트 -->
<button
aria-expanded="false"
onclick="this.setAttribute('aria-expanded', this.getAttribute('aria-expanded') === 'false')">
Menu
</button>
ARIA 속성은 실제 상태와 동기화되어야 합니다. 상태가 변경될 때 속성도 함께 업데이트하세요.
상호작용 가능한 콘텐츠를 숨기지 마세요
<!-- ❌ 버튼이 보조 기술에서 숨겨짐 -->
<button aria-hidden="true">Important Action</button>
<!-- ✅ 장식 요소만 숨기기 -->
<span aria-hidden="true">★</span>
<button>Rate 5 Stars</button>
aria-hidden="true"는 장식적인 요소에만 사용하세요. 중요한 기능이나 콘텐츠를 숨기면 안 됩니다.
불필요한 ARIA 사용을 피하세요
<!-- ❌ 불필요한 aria-label -->
<h1 aria-label="Welcome">Welcome</h1>
<!-- ✅ 텍스트 콘텐츠가 이미 접근 가능 -->
<h1>Welcome</h1>
요소의 텍스트 콘텐츠가 이미 명확하다면 추가 ARIA가 필요 없습니다.
역할 충돌을 만들지 마세요
<!-- ❌ 의미가 혼란스러움 -->
<button role="heading">네비게이션 항목</button>
<!-- ✅ 적절한 요소 사용 -->
<h2>네비게이션</h2>
<button>네비게이션 항목</button>
요소의 기본 의미와 충돌하는 역할을 할당하지 마세요.
ARIA 구현 검증하기
ARIA를 추가한다고 해서 제대로 작동할 거라고 기대해서는 안 됩니다. 반드시 검증해야 합니다.
- 브라우저 개발자 도구
- Chrome과 Firefox에는 접근성 검사기가 내장되어 있습니다. Elements 탭에서 Accessibility 패널을 열어 ARIA 속성이 올바르게 적용되었는지 확인할 수 있습니다.
- 스크린 리더 테스트
- 실제 스크린 리더로 테스트하는 것이 가장 확실합니다. NVDA(Windows), JAWS(Windows), VoiceOver(Mac/iOS), TalkBack(Android)를 사용해 사이트를 탐색해보세요.
- 자동화 도구
- axe DevTools, WAVE, Lighthouse 감사를 실행해 접근성 문제를 자동으로 탐지할 수 있습니다. 이들은 대부분의 일반적인 ARIA 오류를 찾아냅니다.
- 키보드 탐색
- 마우스를 치우고 Tab, Enter, Space, 화살표 키만으로 사이트를 사용해보세요. 모든 기능에 접근할 수 있어야 합니다.
실제 사용자 테스트
가장 신뢰할 수 있는 방법은 실제 보조 기술 사용자와 함께 테스트하는 것입니다. 그들의 피드백은 자동화 도구가 절대 찾지 못하는 문제를 발견할 수 있습니다.
가장 자주 사용되는 ARIA 패턴
- 라벨링
aria-label은 직접 레이블 텍스트를 제공하고,aria-labelledby는 다른 요소의 텍스트를 참조하며,aria-describedby는 추가 설명을 제공합니다.- 상호작용 상태
aria-expanded는 확장 가능한 섹션에,aria-pressed는 토글 버튼에,aria-checked는 사용자 정의 체크박스에,aria-selected는 선택된 옵션에 사용됩니다.- 오류 처리
aria-invalid로 유효하지 않은 필드를 표시하고,aria-errormessage로 오류 텍스트를 연결하며,role="alert"로 즉시 오류를 알립니다.- 동적 콘텐츠
aria-live="polite"는 적절한 시점에 알리고,aria-live="assertive"는 즉시 알리며,aria-busy="true"는 로딩 중임을 나타냅니다.- 탐색
aria-current="page"는 네비게이션 내 현재 페이지를 표시하고,aria-hidden="true"는 장식적 콘텐츠를 숨기며,aria-controls는 트리거와 콘텐츠를 연결합니다.
작은 시작, 큰 변화
이 방대한 목록을 보면 ARIA가 부담스러울 수 있습니다. 하지만 모든 속성을 외울 필요는 없습니다. aria-label, aria-expanded, aria-hidden, aria-live 같은 일반적인 속성부터 시작하세요. 프로젝트에서 필요할 때마다 나머지 속성을 배우면 됩니다.
진정으로 중요한 것은 ARIA의 존재 이유를 이해하는 것입니다. ARIA는 접근 방식에 관계없이 모든 사람이 웹을 사용할 수 있도록 하기 위해 존재합니다. 적절한 ARIA 속성을 추가할 때마다 여러분의 웹사이트는 더 많은 사람들에게 열립니다.
그러니 작은 것부터 시작하세요. 현재 프로젝트에서 하나의 컴포넌트를 선택하세요. 올바른 ARIA 속성을 추가하세요. 스크린 리더로 테스트해 보세요. 얼마나 빨리 익숙해지는지 놀라실 겁니다.
모두가 사용할 수 있을 때 웹은 더 나아집니다.
참고 자료: Dev.to, “99% of developers don’t know these ARIA attributes exist”