browser · high priority
ARIA Patterns — WAI-ARIA Authoring Practices Guide (APG)
커스텀 위젯에 *역할 · 상태 · 키보드* 를 정확히 부여하는 표준 패턴 모음
학습 개요
탄생 배경
쉬운 설명
복잡한 개념을 실생활 비유로 설명합니다.
“음식점의 *메뉴판 점자판*”
시각적으로만 보이는 인터페이스는 *그림 메뉴판* 같습니다. ARIA 는 그 옆에 *점자 라벨* 을 붙여 시각장애 손님(스크린 리더 사용자) 도 *이게 메인 메뉴* (`role="menu"`), *이게 오늘의 추천* (`aria-current="true"`), *이게 재고 없음* (`aria-disabled="true"`) 인지 알 수 있게 해주는 작업입니다. 키보드 인터랙션은 *손이 자유롭지 않은 손님* 에게 *주문 흐름* 을 보장해주는 역할이고, focus trap 은 *주문 창구를 떠나기 전엔 다른 곳으로 못 가게* 가벼운 줄을 치는 것입니다. 그리고 결정적으로 — 점자 라벨이 *틀려 있으면* 차라리 *없는 게 낫습니다*. ARIA 도 같습니다.
핵심 개념
Rules of ARIA (W3C, 5 규칙)
- 네이티브 HTML 요소가 의미를 *이미 가지고 있다면* ARIA 를 쓰지 마라 (
<button>><div role="button">). - 네이티브 의미를 *바꾸지 마라* —
<h1 role="button">같은 안티패턴. - *모든* 인터랙티브 ARIA 컨트롤은 *키보드로 조작 가능* 해야 한다.
- 포커스 가능한 요소에
role="presentation"또는aria-hidden="true"를 *붙이지 마라*. - 인터랙티브 요소는 *접근 가능한 이름(accessible name)* 을 가져야 한다 (텍스트,
aria-label,aria-labelledby).
*안티패턴* vs *올바른 패턴*
<div onClick={...} role="button">— Tab 도 안 됨, Enter/Space 도 처리 안 됨<a href="#" role="button">— 링크의 의미와 충돌<h1 role="button">으로 헤딩 의미 파괴aria-label="버튼"처럼 *역할명 중복* (스크린 리더가 "버튼 버튼" 이라고 읽음)aria-hidden="true"를 *포커스 가능한* 버튼에 부여
1<div role="button" onClick={handleClick}>저장</div>2{/* ❌ Tab 안 됨, Enter 안 됨, accessible name 없음 */}
<button>그대로 사용 — 키보드/포커스/role 자동- *역할명* 은 ARIA 가 자동 발화하므로 라벨에서 빼기
- 시각 라벨이 없으면
aria-label*대신*<button><span class="sr-only">저장</span><Icon/></button> - *상태* 만 ARIA 로 (
aria-pressed,aria-expanded,aria-disabled)
1<button onClick={handleClick} aria-pressed={saved}>2 저장3</button>
"No ARIA is better than Bad ARIA"
WebAIM 의 연간 백만 사이트 조사에서, *ARIA 를 사용한 페이지* 가 사용하지 않은 페이지보다 *평균 41% 더 많은 접근성 오류* 를 가진다는 결과가 매년 반복됩니다. 잘못된 ARIA 가 침묵보다 더 큰 문제를 만든다는 증거입니다.
실무 적용
어떤 상황에서 사용하는가
디자인 시스템에 *커스텀 셀렉트(콤보박스)* 와 *모달* 을 추가하는 PR. 키보드만 쓰는 QA 와 스크린 리더 (NVDA / VoiceOver) 검증을 통과해야 함.
어떻게 적용하는가
(1) 콤보박스는 *네이티브 `<select>` 로 충분한지* 먼저 검토 — 아주 단순한 옵션 선택이면 `<select>` 가 가장 안전. (2) 그렇지 않다면 APG combobox 패턴을 *그대로* 따른다. `role="combobox"`, `aria-expanded`/`aria-controls`/`aria-haspopup`/`aria-autocomplete`/`aria-activedescendant` 5가지 속성을 정확히 동기화. 포커스는 *입력에 유지*, 옵션은 `aria-activedescendant` 로만 가리킴. (3) 모달은 가능한 한 *네이티브 `<dialog>` + `showModal()`* 로 시작 — backdrop · focus trap · Escape 가 자동. 어쩔 수 없이 div 로 만든다면 트리거 element 를 ref 로 기억해두고 close 시 `.focus()` 로 *복귀* 하는 것을 잊지 말기. (4) 키보드 QA 체크리스트 — Tab 만으로 모든 인터랙티브 요소 도달, 화살표로 위젯 내부 이동, Escape 으로 dialog/popup 닫힘, *visible focus indicator* 가 항상 보임. (5) 자동화 — `axe-core` (jest-axe / Playwright) 로 PR 마다 회귀 검증. (6) 실제 스크린 리더 1회 sanity 체크 — VoiceOver (Mac, Cmd+F5) 또는 NVDA (Windows) 로 *직접 들어보기*. 자동화는 자주 빠뜨리는 것을 잡고, 사람의 검증은 *문맥의 의미* 를 잡는다.
흔한 실수와 안티패턴
- `<div onClick>` + `role="button"` 패턴 — 키보드 핸들러 (Enter/Space) 와 `tabindex="0"` 둘 다 빠뜨림.
- 시각 라벨이 이미 있는 버튼에 `aria-label="저장"` 을 또 붙여 *중복 발화*.
- 모달 닫은 뒤 트리거로 포커스 *복귀를 안 함* — 키보드 사용자가 문서 처음으로 튕김.
- `aria-hidden="true"` 가 *포커스 가능한 자식* 을 가지고 있어 스크린 리더에 *유령 포커스* 발생.
- `aria-expanded` 를 마크업에 박아둔 채 JS 가 *동기화하지 않음* — 상태 거짓말.
- `role="combobox"` 만 두고 `aria-controls`/`aria-activedescendant` 등 페어 속성 누락.
- `aria-live` region 을 *매번 새로 마운트* 해서 알림이 발화되지 않음.
흔한 오해
"`<button>` 보다 `<div role="button">` 이 더 유연하니 그걸 쓰는 게 좋다."
교정거의 항상 `<button>` 이 정답. 키보드/포커스/role 을 *공짜로 가져오고* 검색엔진·접근성 도구가 모두 인식한다. 스타일이 문제라면 `<button>` 의 기본 스타일을 *지우는* 것으로 충분.
왜 중요Rule 1 of ARIA — 네이티브 의미가 있다면 ARIA 를 쓰지 마라. `<div role="button">` 은 직접 구현해야 할 것이 *너무 많다*.
"ARIA 속성을 많이 붙일수록 접근성이 좋아진다."
교정잘못된 ARIA 가 *없는 ARIA 보다 더 나쁘다*. WebAIM 통계에 따르면 ARIA 사용 페이지가 평균 41% 더 많은 오류를 가진다.
왜 중요스크린 리더는 ARIA 를 *문자 그대로* 사용자에게 발화한다. 거짓말하는 ARIA 는 사용자의 모델을 망가뜨린다.
"포커스 트랩만 있으면 모달이 접근 가능하다."
교정*트리거로의 포커스 복귀* 가 빠지면 키보드 사용자가 매번 문서 처음으로 튕긴다. focus trap, role, label, escape, *복귀* 5종 세트가 다 필요.
왜 중요APG dialog 패턴이 *복귀* 까지 명시한 이유. focus trap 만으로는 *반쪽짜리*.
면접 질문
답변 방향 힌트
"네이티브 우선", "키보드 핸들러", "tabindex" 가 키워드.
반드시 언급할 키워드
- Rule 1: 네이티브 HTML 의미가 있으면 ARIA 쓰지 마라
- `<div>` 는 기본적으로 *Tab 으로 포커스 받지 못함* (`tabindex="0"` 필요)
- `role="button"` 만 붙여도 *Enter/Space 키 핸들러* 를 직접 구현해야 함
- 폼 안에서 *submit 동작* 이 자동으로 안 일어남
- `disabled` 같은 네이티브 상태를 잃음
- `<button>` 으로 만들면 위 모든 것이 공짜
예상 꼬리 질문
- 시각적으로 링크처럼 보여야 하지만 동작은 버튼인 경우엔 어떻게 합니까?
- `<button type="submit">` 와 `<button type="button">` 을 구분해야 하는 이유는?
자기 점검
Rule 1 of ARIA 를 한 줄로 답하라.
기대 키워드
자주 하는 오해
"ARIA 가 더 정확하니 명시하는 게 좋다" — 네이티브 의미와 충돌하면 *오히려 나쁨*.
모달을 닫을 때 *반드시* 해야 하는 포커스 처리 한 가지는?
기대 키워드
자주 하는 오해
"focus trap 만 있으면 됨" — 닫을 때 *원위치 복귀* 가 빠지면 키보드 사용자가 튕긴다.
Combobox 에서 입력에 포커스를 *유지하면서* 옵션을 강조하는 ARIA 속성은?
기대 키워드
자주 하는 오해
"옵션마다 진짜 포커스를 옮긴다" — 입력의 typing/IME 가 깨진다.
학습 자료
- ARIA Authoring Practices Guide (APG)26개 위젯의 표준 ARIA + 키보드 패턴. 실제 구현 시 *최우선 참고*.DocW3C WAI
- Using ARIA: Roles, States, and PropertiesARIA 의 5규칙과 role/state/property 구분 정리.DocMDN
- APG — Developing a Keyboard InterfaceRoving tabindex · 화살표 이동 · 진입/이탈 표준.DocW3C WAI
- MDN — `aria-modal` 속성모달 dialog 의 의미와 보조 기술 동작.DocMDN
- MDN — combobox rolecombobox role 과 페어 속성 (`aria-expanded` 등) 상세.DocMDN
- WebAIM Million — 연간 백만 사이트 접근성 분석ARIA 오·남용 통계로 *왜 적게 쓰는 게 낫나* 의 데이터 근거.CodeWebAIM