LabHub
배우기 러닝패스 코스

HTML — 태그 하나가 기능을 대신한다 · role 을 붙였더니 더 안 읽힌다 · 이론

접근성 트리는 화면과 다른 문서다

LabHub 에서 이어서 보기

한 줄 요약

브라우저는 DOM 에서 접근성 트리(accessibility tree) 를 따로 만든다. 스크린리더가 읽는 것은 화면이 아니라 그 트리이고, ARIA 는 그 트리만 고친다 — 동작은 하나도 바꾸지 않는다.

왜 이게 필요했나

접근성을 고치라는 지적을 받으면 사람들은 대개 rolearia-* 를 붙인다. 그런데 그 결과가 원래보다 나빠지는 경우가 자주 있다. 실제로 ARIA 를 많이 쓴 페이지가 안 쓴 페이지보다 오류율이 높다는 조사가 반복해서 나온다.

이유는 단순하다. ARIA 는 약속이지 구현이 아니다. role="button" 을 붙이면 보조기기에 "이건 버튼이다" 라고 말한 것이고, 그 순간부터 버튼처럼 동작하게 만드는 것은 전부 개발자 몫이 된다. 말만 하고 구현을 안 하면, 스크린리더 사용자는 버튼이라고 안내받고 눌렀는데 아무 일도 안 일어나는 화면을 만난다. 아무 말도 안 한 것보다 나쁘다.

어떻게 동작하나

DOM 에서 접근성 트리가 만들어질 때 요소마다 네 가지가 정해진다.

| 항목 | 무엇인가 | 예 |
|---|---|---|
| role | 이것이 무엇인가 | button · link · heading · list |
| name | 뭐라고 부르는가 | "저장" |
| state | 지금 어떤 상태인가 | 눌림 · 펼침 · 비활성 |
| value | 어떤 값인가 | 슬라이더의 40 |

네이티브 HTML 요소는 이 네 가지를 이미 갖고 태어난다. <button> 은 role 이 button 이고, 안의 글자가 name 이 되고, disabled 가 state 가 되고, 초점과 Enter·Space 처리가 브라우저에 들어 있다.

<div role="button"> 은 이 중 role 하나만 얻는다. 나머지 셋과 동작은 전부 직접 만들어야 한다. [MDN 의 접근성 트리 설명](https://developer.mozilla.org/en-US/docs/Glossary/Accessibility_tree)과 [ARIA in HTML](https://www.w3.org/TR/html-aria/)이 이 관계를 정리해 둔다.

다섯 가지 규칙

[WAI-ARIA 사용 규칙](https://www.w3.org/WAI/ARIA/apg/practices/read-me-first/)은 다섯 줄로 요약된다. 순서대로 중요하다.

1. 쓸 수 있는 HTML 요소가 있으면 그것을 쓴다. ARIA 는 대응 요소가 없을 때만
2. 네이티브 의미를 바꾸지 않는다. <h2 role="tab"> 은 제목 목록에서 그 항목을 지운다
3. 모든 대화형 ARIA 위젯은 키보드로 조작할 수 있어야 한다
4. 초점을 받을 수 있는 요소에 role="presentation" 이나 aria-hidden="true" 를 쓰지 않는다
5. 모든 대화형 요소에는 접근 가능한 이름이 있어야 한다

두 번째 규칙이 가장 조용히 사고를 낸다. role 은 더하는 것이 아니라 덮어쓰는 것이다. <ul role="presentation"> 을 쓰면 그 목록은 더 이상 목록이 아니고, 스크린리더는 "항목 다섯 개" 라고 말해 주지 않는다. 디자인상 점(bullet)을 지우려고 붙였다가 구조를 통째로 지우는 일이 실제로 흔하다.

상태는 거짓말을 하기 쉽다

aria-expanded, aria-selected, aria-checked, aria-current 같은 상태 속성은 직접 갱신해야 한다. 화살표 아이콘을 돌리는 CSS 는 자동으로 갱신되지만 이 속성은 아니다.

그래서 자주 나는 사고가 이것이다 — 패널은 열렸는데 aria-expanded 는 여전히 false 다. 눈으로 보는 사람에게는 열린 화면이고, 스크린리더 사용자에게는 닫힌 화면이다. 둘 중 하나는 거짓말인데, 거짓말하는 쪽이 언제나 보조기기 쪽이다.

aria-controls, aria-labelledby, aria-describedby 는 id 로 다른 요소를 가리킨다. 그 id 가 없으면 브라우저는 오류를 내지 않고 조용히 무시한다. 컴포넌트를 옮기거나 조건부로 렌더링하다가 참조가 끊기는 일이 잦은데, 화면에는 아무 변화가 없어서 아무도 모른다.

라이브 영역

검색 결과 개수, 저장 완료 안내, 오류 메시지. 나중에 나타나는 글자는 그것만으로는 읽히지 않는다. 스크린리더는 이미 다른 곳을 읽고 있기 때문이다.

주의할 점이 하나 있다. 라이브 영역은 미리 DOM 에 있어야 한다. 요소를 통째로 새로 만들면서 role="alert" 를 붙이면 읽히지 않는 경우가 많다. 빈 컨테이너를 먼저 두고 그 안의 글자를 바꾸는 것이 안전하다.

현장에서 만나는 모습

세 가지가 반복해서 나온다.

첫째, 아이콘 버튼에 이름이 없다. <button><svg/></button> 은 이름이 없어서 "버튼" 이라고만 읽힌다. aria-label="닫기" 한 줄이면 끝나는데 가장 자주 빠진다.

둘째, 장식 아이콘을 안 가린다. 글자로 그린 아이콘(, )이 그대로 읽혀서 "검은 별 즐겨찾기" 같은 소리가 난다. aria-hidden="true" 를 붙이면 조용해진다.

셋째, 디자인 시스템이 role 을 남발한다. 한 번 잘못 쓴 role 이 컴포넌트를 타고 사이트 전체로 퍼진다. 이건 개별 페이지를 고쳐서는 못 막고, 컴포넌트 단위로 잡아야 한다.

기억할 것은 하나다. 나쁜 ARIA 는 없는 것만 못하다. 고치기 전에 먼저 물어야 할 질문은 "어떤 role 을 붙일까" 가 아니라 "이걸 하는 HTML 요소가 이미 있지 않은가" 다.

다음 퀴즈에서 확인할 것

접근성 트리가 무엇을 담는지, role 이 무엇을 덮어쓰는지, 상태 속성과 라이브 영역이 어떤 방식으로 조용히 깨지는지를 확인합니다.