FEInterview Prep

browser · high priority

ARIA Patterns — WAI-ARIA Authoring Practices Guide (APG)

커스텀 위젯에 *역할 · 상태 · 키보드* 를 정확히 부여하는 표준 패턴 모음

advanced 난이도5시간토스카카오네이버배민당근라인
시작 전
이해도
매우 낮음

학습 개요

탄생 배경

쉬운 설명

복잡한 개념을 실생활 비유로 설명합니다.

음식점의 *메뉴판 점자판*

시각적으로만 보이는 인터페이스는 *그림 메뉴판* 같습니다. ARIA 는 그 옆에 *점자 라벨* 을 붙여 시각장애 손님(스크린 리더 사용자) 도 *이게 메인 메뉴* (`role="menu"`), *이게 오늘의 추천* (`aria-current="true"`), *이게 재고 없음* (`aria-disabled="true"`) 인지 알 수 있게 해주는 작업입니다. 키보드 인터랙션은 *손이 자유롭지 않은 손님* 에게 *주문 흐름* 을 보장해주는 역할이고, focus trap 은 *주문 창구를 떠나기 전엔 다른 곳으로 못 가게* 가벼운 줄을 치는 것입니다. 그리고 결정적으로 — 점자 라벨이 *틀려 있으면* 차라리 *없는 게 낫습니다*. ARIA 도 같습니다.

핵심 개념

Rules of ARIA (W3C, 5 규칙)

  1. 네이티브 HTML 요소가 의미를 *이미 가지고 있다면* ARIA 를 쓰지 마라 (<button> > <div role="button">).
  2. 네이티브 의미를 *바꾸지 마라* — <h1 role="button"> 같은 안티패턴.
  3. *모든* 인터랙티브 ARIA 컨트롤은 *키보드로 조작 가능* 해야 한다.
  4. 포커스 가능한 요소에 role="presentation" 또는 aria-hidden="true" 를 *붙이지 마라*.
  5. 인터랙티브 요소는 *접근 가능한 이름(accessible name)* 을 가져야 한다 (텍스트, aria-label, aria-labelledby).

*안티패턴* vs *올바른 패턴*

❌ 잘못된 ARIA
  • <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 없음 */}
✅ 네이티브 + 최소 ARIA
  • <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 를 한 줄로 답하라.

기대 키워드

네이티브 HTMLARIA 쓰지 마라암묵적 role

자주 하는 오해

"ARIA 가 더 정확하니 명시하는 게 좋다" — 네이티브 의미와 충돌하면 *오히려 나쁨*.

모달을 닫을 때 *반드시* 해야 하는 포커스 처리 한 가지는?

기대 키워드

트리거 요소복귀`document.activeElement`

자주 하는 오해

"focus trap 만 있으면 됨" — 닫을 때 *원위치 복귀* 가 빠지면 키보드 사용자가 튕긴다.

Combobox 에서 입력에 포커스를 *유지하면서* 옵션을 강조하는 ARIA 속성은?

기대 키워드

`aria-activedescendant`논리적 강조DOM 포커스 유지

자주 하는 오해

"옵션마다 진짜 포커스를 옮긴다" — 입력의 typing/IME 가 깨진다.

학습 자료