웹 접근성
눈으로 보면 멀쩡한 페이지가 있다. 제목은 크고 굵고, 사진은 선명하고, 신청 버튼은 초록색으로 눈에 띈다. 그런데 같은 페이지를 화면 낭독기로 들으면 "img_0231 점 jpg, 이미지"가 먼저 나오고, 제목으로 건너뛰는 단축키를 누르면 "제목 없음"이라는 대답이 돌아온다. 마우스 없이 Tab 키로 옮겨 가면 포커스가 어디 있는지 보이지 않는다. 보이는 화면과 기계가 읽는 화면은 서로 다른 두 화면이다. 이 장은 그 두 번째 화면을 디자인하는 법을 본다.
- 웹 접근성이 장애가 있는 사람뿐 아니라 나이, 상황, 기기의 제약을 가진 모두의 문제라는 것과 WCAG 2.2·KWCAG 2.2의 관계를 안다.
- 의미 있는 HTML이 접근성 트리를 거쳐 화면 낭독기의 제목·랜드마크·링크 목록이 되는 과정을 설명한다.
- 키보드만으로 페이지를 쓸 때의 포커스 순서, 포커스 표시, 가려진 포커스를 점검한다(2.1.1, 2.4.3, 2.4.7, 2.4.11).
- 그림의 역할에 따라 대체 텍스트를 쓰거나 비운다(1.1.1).
- 폼 오류를 색이 아니라 글자로, 칸에 연결해 알린다(1.4.1, 3.3.1).
- 글자와 UI의 대비(1.4.3, 1.4.11), 200% 확대(1.4.4)와 320px 리플로(1.4.10)를 직접 재 본다.
접근성은 누구를 위한가
웹 접근성(Web accessibility)은 장애가 있든 없든, 어떤 기기와 방법으로 접속하든 웹의 정보와 기능을 쓸 수 있게 하는 것이다. "장애인을 위한 추가 기능"으로 생각하기 쉽지만, 실제로 접근성의 덕을 보는 사람은 훨씬 넓다.
- 보지 않고 듣는 사람. 시각장애가 있는 사람은 화면 낭독기(Screen reader)로 웹을 듣는다. 화면 낭독기는 페이지의 글자와 구조를 소리(또는 점자 디스플레이)로 바꾸는 프로그램이다. 윈도의 NVDA와 JAWS, 애플 기기의 VoiceOver, 안드로이드의 TalkBack이 널리 쓰이고, 국내에서는 한국어에 맞춘 센스리더도 많이 쓴다.
- 마우스를 쓰지 않는 사람. 손 떨림이나 마비가 있는 사람, 화면 낭독기 사용자, 키보드가 빠른 숙련자는 Tab과 화살표, Enter로 페이지를 옮겨 다닌다. 머리 움직임이나 스위치 하나로 키보드 입력을 흉내 내는 장치도 같은 길을 쓴다.
- 크게 키워 보는 사람. 저시력인 사람, 그리고 한도경처럼 나이가 들어 휴대폰 글자를 가장 크게 해 둔 사람은 화면을 200%, 400%로 키운다(2장).
- 잠시 제약이 생긴 사람. 한 손에 아이를 안은 사람, 한낮의 햇빛 아래에서 화면을 보는 사람, 손목을 다친 사람, 시끄러운 지하철에서 소리 없이 영상을 보는 사람.
그래서 접근성을 지키면 가장 어려운 조건의 사람을 위해 고친 것이 모두를 편하게 하는 일이 자주 생긴다. 큰 터치 타깃(7장), 칸 위에 붙은 라벨(9장), 줄인 움직임(10장)이 그랬다. 이 장은 그 가운데 아직 다루지 않은 것, 곧 보이지 않는 구조를 본다.
기준: WCAG 2.2와 KWCAG 2.2
웹 접근성의 국제 기준은 W3C의 WCAG(Web Content Accessibility Guidelines)다. 최신판인 WCAG 2.2는 2023년 10월 W3C 권고안이 되었다. WCAG는 네 가지 원칙, 곧 인식할 수 있어야 하고(Perceivable), 조작할 수 있어야 하고(Operable), 이해할 수 있어야 하고(Understandable), 견고해야 한다(Robust)는 원칙 아래 검사할 수 있는 성공 기준(Success criterion, SC)을 번호로 둔다. 번호의 첫 자리가 원칙이다. 예를 들어 1.4.3은 원칙 1(인식)의 지침 4(구별)의 셋째 기준인 "대비(최소)"다. 기준마다 A, AA, AAA 세 수준이 있고, 법과 조달 규정은 대개 AA까지를 요구한다.
국내에는 WCAG를 바탕으로 우리 실정에 맞게 만든 한국형 웹 콘텐츠 접근성 지침(KWCAG) 2.2가 있다. 공공기관 웹사이트와 국내 웹 접근성 품질인증 심사가 이 지침을 기준으로 삼고, 「장애인차별금지 및 권리구제 등에 관한 법률」은 웹사이트 같은 전자정보에 대한 접근성 보장을 요구한다. 두 지침은 다루는 내용이 대부분 겹치지만 항목의 묶음과 번호는 다르다. 이 장은 국제 기준인 WCAG 2.2의 번호로 설명한다. 국내 사이트를 만든다면 KWCAG 2.2의 검사 항목에서 같은 내용을 찾아 함께 확인한다.
| 번호 | 성공 기준(WCAG 2.2) | 수준 | 이 장의 절 |
|---|---|---|---|
| 1.1.1 | 텍스트 아닌 콘텐츠(Non-text Content) | A | 대체 텍스트 |
| 1.3.1 | 정보와 관계(Info and Relationships) | A | 의미 있는 HTML, 들리는 폼 |
| 1.4.1 | 색의 사용(Use of Color) | A | 들리는 폼, 대비 |
| 1.4.3 | 대비(최소)(Contrast (Minimum)) | AA | 대비 |
| 1.4.4 | 글자 크기 조정(Resize Text) | AA | 확대와 리플로 |
| 1.4.10 | 리플로(Reflow) | AA | 확대와 리플로 |
| 1.4.11 | 텍스트 아닌 대비(Non-text Contrast) | AA | 대비 |
| 2.1.1 | 키보드(Keyboard) | A | 키보드와 포커스 |
| 2.4.1 | 블록 건너뛰기(Bypass Blocks) | A | 키보드와 포커스 |
| 2.4.3 | 포커스 순서(Focus Order) | A | 키보드와 포커스 |
| 2.4.4 | 링크 목적(문맥 안에서)(Link Purpose (In Context)) | A | 의미 있는 HTML |
| 2.4.7 | 포커스 표시(Focus Visible) | AA | 키보드와 포커스 |
| 2.4.11 | 가려지지 않는 포커스(최소)(Focus Not Obscured (Minimum)) | AA | 키보드와 포커스 |
| 2.5.8 | 타깃 크기(최소)(Target Size (Minimum)) | AA | 7장 |
| 3.3.1 | 오류 식별(Error Identification) | A | 들리는 폼 |
| 4.1.2 | 이름, 역할, 값(Name, Role, Value) | A | 의미 있는 HTML |
표의 한국어 이름은 이 책이 뜻을 옮긴 것이다. 2.4.11과 2.5.8은 WCAG 2.2에서 새로 생긴 기준이다. 기준의 정확한 문장과 예외는 W3C의 원문(Web Content Accessibility Guidelines 2.2)과 각 기준의 해설 문서(Understanding WCAG 2.2)에서 확인한다.
의미 있는 HTML과 제목 구조
브라우저는 HTML을 읽어 화면을 그리는 동시에 접근성 트리(Accessibility tree)를 만든다. 접근성 트리는 페이지의 요소마다 역할(버튼인가, 링크인가, 제목인가), 이름(무엇이라고 부르는가), 상태(눌렸는가, 펼쳐졌는가, 잘못 입력되었는가)를 적은 목록이다. 화면 낭독기 같은 보조 기술(Assistive technology)은 픽셀이 아니라 이 트리를 읽는다. 그래서 눈에 똑같이 보이는 두 버튼이 보조 기술에는 전혀 다르게 전해질 수 있다.
button 요소는 역할, 이름, 키보드 조작을 브라우저가 공짜로 붙여 준다(4.1.2). div에 클릭 처리만 붙인 "가짜 버튼"은 마우스로는 눌리지만, 보조 기술에는 그냥 글자이고 키보드로는 갈 수도 없다(2.1.1). 실제 화면 낭독기의 말은 제품과 설정에 따라 조금씩 다르다.그림 1의 교훈은 단순하다. 그 일을 하는 HTML 요소가 있으면 그것을 쓴다. 누르면 무언가 일어나는 것은 button, 다른 곳으로 가는 것은 a, 칸의 이름은 label이다. 페이지의 큰 덩어리에도 이름표가 있다. 머리글은 header, 메뉴는 nav, 본문은 main, 바닥글은 footer이고, 보조 기술은 이것을 랜드마크(Landmark)로 모아 "본문으로 바로 가기" 같은 이동을 돕는다. 글의 뼈대는 제목 요소 h1~h6가 만든다.
화면 낭독기 사용자는 긴 페이지를 처음부터 끝까지 듣지 않는다. 눈으로 보는 사람이 제목만 훑으며 내려가듯, 단축키 하나로 다음 제목으로 건너뛰거나 페이지의 제목·랜드마크·링크만 뽑은 목록을 열어 고른다. WebAIM이 여러 차례 실시한 화면 낭독기 사용자 설문에서도, 긴 페이지에서 정보를 찾을 때 제목으로 건너뛰는 방법이 가장 흔한 답으로 나왔다. 아래는 책방 E의 행사 일정 페이지를 두 가지 HTML로 짠 것이다. 화면은 거의 같다. 낭독기가 뽑아 보는 목록을 바꿔 가며 비교한다.
화면 낭독기가 뽑아 보는 목록
div라서다. ② 링크 목록으로 바꾼다. "자세히 보기"가 세 번, 파일 이름 하나, "여기"가 하나 있다. 목록만 들어서는 어디로 가는지 알 수 없다(2.4.4). ③ "고친 뒤"로 바꾸고 목록의 항목을 눌러 본다. 화면의 어느 부분인지 빨간 틀로 표시된다. 제목 단계가 1 → 2 → 3으로 건너뛰지 않는다. ④ "태그 이름 겹쳐 보기"로 두 판의 차이가 화면이 아니라 태그에 있다는 것을 확인한다. (가상 페이지다. 낭독기마다 목록 이름과 읽는 말이 조금씩 다르다. 넘기는 횟수는 한 항목씩 읽어 내려갈 때와 제목 단위로 건너뛸 때를 단순히 센 것이다.)"고치기 전" 페이지에서 화면 낭독기 사용자가 세 번째 모임을 찾으려면 로고와 메뉴, 소개 글, 앞의 두 모임을 차례로 다 들어야 한다. 제목이 있으면 다섯 번 만에 닿는다. 눈으로 보는 사람에게 글자 크기와 굵기가 하는 일을 화면 낭독기 사용자에게는 제목 요소가 한다. 그래서 제목 요소는 크기를 고르는 도구가 아니라 구조를 적는 도구다. "이 글자는 작게 보이고 싶으니 h4를 쓴다"가 아니라, 구조상 셋째 단계면 h3을 쓰고 크기는 CSS로 정한다(6장의 글자 크기 단계).
- 페이지마다 그 페이지의 주제를 말하는
h1을 하나 둔다. 로고에h1을 쓰면 모든 페이지의 첫 제목이 "책방 E"가 된다. - 단계를 건너뛰지 않는다.
h2아래에 바로h4가 오면 듣는 사람은 중간에 무엇이 빠졌는지 헷갈린다. - 링크 문구는 그것만 들어도 목적지를 알게 쓴다(2.4.4). "자세히 보기" 대신 "겨울 낭독회 자세히 보기". 눈으로 보이는 글자를 짧게 두고 싶다면 보이지 않는 글자를 덧붙이는 방법도 있지만, 보이는 글자 자체를 구체적으로 쓰는 것이 모두에게 낫다.
- 페이지의 언어를
<html lang="ko">로 적는다(3.1.1). 그래야 낭독기가 한국어 음성으로 읽는다.
키보드와 포커스
키보드로 웹을 쓰는 방법은 단순하다. Tab은 다음 조작할 수 있는 요소로, Shift + Tab은 이전 요소로 옮긴다. Enter는 링크와 버튼을, Space는 버튼과 체크 상자를 누른다. 지금 키 입력을 받는 요소에 포커스(Focus)가 있다고 하고, 그 위치를 보여 주는 테두리를 포커스 표시(Focus indicator), 흔히 포커스 링이라고 부른다. 마우스 사용자의 커서와 같은 것이다. 커서가 보이지 않는 마우스를 상상해 보면 포커스 표시를 지우는 일이 얼마나 큰 일인지 알 수 있다.
키보드 접근성에 걸린 WCAG 기준은 네 가지가 핵심이다.
- 2.1.1 키보드(A). 마우스로 할 수 있는 모든 기능을 키보드로도 할 수 있어야 한다. 그림 1의 가짜 버튼이 대표적인 실패다.
- 2.4.3 포커스 순서(A). 포커스가 옮겨 가는 순서가 의미와 조작의 흐름을 지켜야 한다. 대개는 HTML에 적힌 순서이며, 화면에 보이는 읽는 순서와 맞아야 한다.
- 2.4.7 포커스 표시(AA). 키보드로 조작할 때 포커스가 어디 있는지 보여야 한다.
- 2.4.11 가려지지 않는 포커스(최소)(AA). 포커스를 받은 요소가 페이지가 만든 다른 콘텐츠(고정 머리글, 하단 띠, 쿠키 안내 같은 것)에 완전히 가려져서는 안 된다. 일부라도 보이지 않게 하라는 더 엄격한 기준은 2.4.12(AAA)다.
아래는 책방 E의 모임 신청 페이지다. 마우스로는 아무 문제가 없다. Tab 단추를 눌러 키보드 사용자처럼 옮겨 가 본다. 지나온 순서가 번호로 남는다.
키보드만으로 모임 신청하기
tabindex="1"을 준 탓, 2.4.3), 포커스 표시를 지웠고(outline: none, 2.4.7) 그 버튼은 고정 띠에 완전히 가려졌다(2.4.11). ② 계속 눌러 끝까지 간다. 날짜 칩 세 개는 div라 한 번도 들르지 않는다(2.1.1). 입력 칸은 깜박이는 커서로만 겨우 알 수 있다. ③ "고친 뒤"로 바꿔 처음부터 다시 한다. 첫 Tab에서 "본문 바로가기" 링크가 나타나고(2.4.1), 순서는 화면을 읽는 순서와 같으며, 굵은 짙은 테두리가 따라온다(짙은 바닥글 안에서는 크림색). 신청하기에 오면 페이지가 띠 높이만큼 더 올라가 버튼이 띠 위에 보인다. (가상 페이지다. 실제 브라우저는 포커스를 받은 요소가 보이도록 스스로 스크롤하지만, 고정된 띠의 높이는 모른다. "고친 뒤"는 CSS의 scroll-padding-bottom으로 띠 높이를 알려 준 경우다.)"고치기 전"의 문제들은 대부분 누군가 좋은 뜻으로 넣은 코드에서 생겼다. 클릭할 때 생기는 테두리가 보기 싫어 outline: none을 넣었고, 중요한 버튼에 먼저 가게 하려고 tabindex="1"을 넣었고, 모양을 자유롭게 꾸미려고 칩을 div로 만들었다. 고치는 방법도 거의 정해져 있다.
/* 마우스 클릭에는 표시하지 않고, 키보드 조작에는 뚜렷하게 */
:focus-visible {
outline: 3px solid #22342a; /* 짙은 색(7장의 링): 흰 바탕과 13.19:1 */
outline-offset: 2px;
}
/* 고정 머리글·띠의 높이만큼 포커스 이동 때 여유를 둔다 */
html { scroll-padding-top: 64px; scroll-padding-bottom: 64px; }
:focus-visible은 브라우저가 "이 포커스는 표시가 필요하다"고 판단할 때만 걸리는 선택자다. 대개 마우스로 버튼을 누를 때는 걸리지 않고 키보드로 옮길 때는 걸리므로, 테두리가 보기 싫다는 이유로 포커스 표시를 통째로 지울 필요가 없다. 순서는 tabindex의 양수 값으로 바꾸지 말고 HTML의 순서 자체를 화면 순서에 맞춘다. tabindex="0"(제 순서대로 포커스 받기)과 tabindex="-1"(스크립트로만 포커스 주기)은 쓸모가 있지만, 1 이상의 값은 페이지 전체의 순서를 흔든다. 날짜 칩은 button이나 라디오 단추로 만들면 키보드 조작이 저절로 따라온다.
포커스 표시가 얼마나 뚜렷해야 하는지는 2.4.13 포커스 모양(AAA)이 크기와 대비를 정해 두었다. AA 수준에서 구체적인 수치를 요구하지는 않지만, 표시도 결국 UI 요소이므로 바탕과 3:1 이상 대비(1.4.11, 아래 "대비" 절)를 주는 것이 안전하다. 버튼 색과 같은 색의 얇은 테두리는 버튼에 붙어 버려 잘 보이지 않는다. 그래서 위 코드처럼 테두리를 조금 띄운다(outline-offset).
대체 텍스트
화면 낭독기는 그림을 볼 수 없다. 그림 대신 읽어 줄 글을 대체 텍스트(Alternative text, alt text)라고 하고, HTML에서는 img 요소의 alt 속성에 쓴다. 1.1.1 텍스트 아닌 콘텐츠(A)는 정보를 전하는 그림에 같은 목적의 글을 붙이라고 하고, 꾸밈뿐인 그림은 보조 기술이 무시하도록 하라고 한다. 대체 텍스트는 그림을 볼 수 없는 사람뿐 아니라 통신이 느려 그림이 오지 않은 사람, 검색 엔진에도 전해진다.
좋은 대체 텍스트는 "이 그림이 무엇처럼 생겼나"보다 "이 그림이 이 자리에서 무슨 일을 하나"를 쓴다. 같은 사진도 자리에 따라 대체 텍스트가 달라진다.
alt=""는 "대체 텍스트 없음"이 아니라 "읽지 말 것"이라는 분명한 표시다. alt 속성 자체를 빼면 많은 낭독기가 대신 파일 이름을 읽는다.아래 행사 소개 페이지에는 그림이 넷 있다. 로고 링크, 낭독회 사진, 꽃잎 모양 구분선, 일정이 적힌 포스터. 대체 텍스트를 네 가지 방식으로 붙여 보고 화면 낭독기가 위에서부터 읽어 주는 말을 듣는다(아래 목록).
대체 텍스트를 귀로 듣기
"역할에 맞게"에서 포스터를 다룬 방식을 눈여겨본다. 포스터의 글자(날짜, 시간, 장소)를 대체 텍스트에 다 몰아넣는 대신, 짧은 이름만 남기고 정보는 본문 글자로 옮겼다. 그림 속 글자는 확대하면 흐려지고, 검색되지 않고, 번역되지 않고, 낭독기 사용자가 단어 단위로 되짚어 듣기도 어렵다. 그림 속에만 있는 정보는 대개 HTML 글자로도 있어야 한다. 차트나 지도처럼 짧은 글로 요약하기 어려운 그림은 그림 가까이에 자세한 설명이나 표를 둔다.
| 그림 | 나쁜 예 | 좋은 예 |
|---|---|---|
| 로고(첫 화면으로 가는 링크) | alt="logo" | alt="책방 E 첫 화면" |
| 행사 사진 | alt="img_0231", alt="사진" | alt="서가 앞에 둥글게 앉은 손님들이 낭독을 듣고 있다" |
| 꾸밈 무늬, 구분선 | alt="꽃잎 장식" | alt="" 또는 CSS 배경으로 |
| 돋보기 아이콘만 있는 검색 단추 | alt="돋보기" | alt="검색"(동작을 쓴다) |
| 신간 표지(목록 안, 옆에 책 제목 글자가 있음) | 책 제목을 한 번 더 | alt=""(같은 말을 두 번 듣지 않게) |
"사진", "이미지", "그림"이라는 말은 대체 텍스트에 넣지 않는다. 낭독기가 이미 "이미지"라고 알려 준다. 길이에 정해진 상한은 없지만, 한두 문장 안에서 이 자리의 목적에 필요한 것만 고른다. 서윤은 수채화가답게 처음에 사진마다 빛과 색을 길게 묘사했다가, 낭독회 안내 페이지에서 필요한 것은 "어떤 분위기의 모임인가"뿐이라는 것을 알고 줄였다.
들리는 폼
9장에서 모임 신청 폼의 라벨을 칸 위에 붙이고, 칸을 떠날 때 검증해서 오류를 칸 바로 아래에 보여 주기로 했다. 눈으로 보는 사람에게는 충분하다. 화면 낭독기 사용자에게는 두 가지가 더 필요하다. 오류가 글자로 있어야 하고(색과 테두리만으로는 들리지 않는다), 그 글자가 칸에 연결되어 있어야 한다. 낭독기 사용자는 칸에 포커스가 왔을 때 그 칸의 이름과 상태를 듣는다. 칸 아래에 적힌 글자는 화면에서는 바로 옆이지만, 연결이 없으면 낭독기는 그 칸을 떠나 계속 읽어 내려가야 만난다.
오류를 보고, 듣기
연결은 HTML 속성 몇 개로 한다. aria-invalid는 "이 칸의 값이 잘못되었다"는 상태를, aria-describedby는 "이 칸의 설명은 저 요소다"라는 관계를 접근성 트리에 적는다.
<label for="phone">연락처</label>
<input id="phone" type="tel" autocomplete="tel"
aria-invalid="true" aria-describedby="phone-err">
<p id="phone-err">오류: 숫자 10~11자리로 적어 주세요. 예: 010-1234-5678</p>
3.3.1 오류 식별(A)은 오류가 난 항목을 알려 주고 그 오류를 글자로 설명하라고 한다. 오류 문구는 무엇이 잘못되었는지와 어떻게 고치는지를 함께 쓴다. "형식이 올바르지 않습니다"보다 "숫자 10~11자리로 적어 주세요"가 낫다. 신청하기를 눌렀을 때 오류가 여러 개라면 포커스를 첫 오류 칸으로 옮겨, 낭독기 사용자가 페이지의 어디서 무엇을 고쳐야 하는지 찾아 헤매지 않게 한다. "신청이 접수되었습니다" 같은 결과 알림처럼 포커스를 옮기지 않고 나타나는 메시지는 role="status" 같은 상태 영역에 넣어 낭독기가 읽게 한다(4.1.3 상태 메시지, AA).
aria-* 속성(ARIA)은 HTML만으로 표현할 수 없는 역할과 상태를 접근성 트리에 적는 도구다. 강력한 만큼 잘못 쓰면 오히려 망친다. div에 role="button"을 붙이면 낭독기는 "버튼"이라고 읽지만, 키보드로 포커스를 받고 Enter와 Space로 눌리는 동작은 여전히 직접 만들어야 한다. W3C의 ARIA 사용 안내(Using ARIA)가 첫 규칙으로 "같은 일을 하는 HTML 요소나 속성이 있으면 그것을 쓰라"고 하는 이유다. 위 예처럼 HTML 요소로 뼈대를 세우고, 모자란 관계와 상태만 ARIA로 보탠다.
대비와 색
글자와 바탕의 밝기 차이는 ColorBook 13장에서 WCAG 대비비로 쟀다. 대비비는 두 색의 상대 휘도 가운데 큰 값을 \(L_1\), 작은 값을 \(L_2\)라 할 때 \((L_1 + 0.05) / (L_2 + 0.05)\)이며 1에서 21까지다. 웹 화면에서 다시 짚을 것은 세 가지다.
- 1.4.3 대비(최소)(AA). 본문 글자는 바탕과 4.5:1 이상, 큰 글자는 3:1 이상. 큰 글자는 18포인트(약 24 CSS px) 이상, 또는 굵게 14포인트(약 18.66 CSS px) 이상이다. 입력 칸의 안내 글자나 회색 보조 설명처럼 일부러 흐리게 만든 글자도 정보를 담으면 이 기준을 지킨다.
- 1.4.11 텍스트 아닌 대비(AA). 입력 칸의 테두리, 체크 상자, 아이콘, 포커스 표시처럼 "이것이 무엇이고 어떤 상태인지 알아보는 데 필요한" UI 요소와 그래픽은 이웃한 색과 3:1 이상. 흰 바탕 위 연회색 테두리만 있는 입력 칸이 흔한 실패다.
- 1.4.1 색의 사용(A). 색만으로 정보를 전하지 않는다. 앞 절의 빨간 테두리, 밑줄 없이 색만 다른 본문 속 링크가 그 예다.
아래 신청 카드에서 세 요소의 색을 하나씩 골라 오른쪽 띠의 손잡이를 끌어 밝기를 바꾼다. 띠 위의 눈금이 기준이 넘어가는 자리다.
안내 글자와 테두리의 대비
ART.contrast로, 색각 시뮬레이션은 ART.simulateCVD(Machado 외 2009)로 계산했다.)대비 기준을 지키면 디자인이 칙칙해진다는 걱정을 자주 듣는다. 실제로는 기준선 근처의 색을 고르는 법을 익히면 된다. 연회색 안내 글자는 4.5 눈금 바로 위의 회색으로, 테두리는 3 눈금 바로 위로 옮기면 화면의 인상은 크게 바뀌지 않는다. 다크 모드의 색도 따로 재야 한다(6장). 대비비 공식은 사람의 지각과 어긋나는 구석이 있어 WCAG 3 초안에서 새 방식(APCA)이 검토되고 있지만, 아직 표준이 아니므로 이 책은 WCAG 2의 대비비를 기준으로 쓴다.
확대와 리플로
저시력인 사람은 브라우저 확대(Ctrl 또는 Command와 +)로 페이지 전체를 키운다. 확대하면 CSS 픽셀 하나가 기기 픽셀 여러 개가 되므로, 같은 화면에 들어가는 CSS 폭이 줄어든다(2장). 폭 1,280px 화면을 200%로 키우면 페이지가 받는 폭은 640px, 400%로 키우면 320px다. 확대는 결국 좁은 화면과 같은 문제다.
- 1.4.4 글자 크기 조정(AA). 보조 기술 없이 글자를 200%까지 키워도 내용이나 기능을 잃지 않아야 한다. 글자가 잘리거나 겹치거나, 높이를 고정한 상자 밖으로 넘쳐 사라지면 실패다.
- 1.4.10 리플로(AA). 세로로 읽는 콘텐츠는 폭 320 CSS px(1,280px 화면을 400%로 확대한 것과 같은 폭)에서 가로와 세로 두 방향으로 스크롤하지 않고 읽을 수 있어야 한다. 지도, 표, 도표처럼 2차원 배치가 의미의 일부인 것은 예외다.
- 1.4.12 글자 간격(AA). 사용자가 행간을 글자 크기의 1.5배, 문단 간격을 2배, 자간을 0.12배, 낱말 간격을 0.16배로 늘려도 내용이 잘리지 않아야 한다. 높이를 px로 못 박은 상자가 주로 문제를 일으킨다.
아래는 노트북 화면(폭 1,280px)에서 연 책방 E의 옛 사이트(폭 960px 고정 템플릿)와 새 사이트(유동 레이아웃)다. 확대 배율을 올려 본다.
400%까지 키워 보기
리플로를 지키는 방법은 5장의 반응형 레이아웃 그 자체다. 폭을 px로 못 박지 않고, 좁아지면 단을 줄이고, 글자 크기는 rem으로 적어 사용자의 글자 크기 설정을 따른다. 그래서 반응형으로 잘 만든 사이트는 리플로 기준을 거의 저절로 통과한다. 남는 함정은 높이다. 행 높이나 카드 높이를 px로 고정하면 확대나 글자 간격 조정 때 글자가 상자 밖으로 넘친다. 높이는 내용이 정하게 둔다.
점검하는 법
접근성은 마지막에 한 번 "검사"하는 것이 아니라 디자인 단계부터 정하는 것이다. 시안에 제목 단계를 적어 두고, 포커스 상태를 그리고, 사진마다 대체 텍스트를 함께 쓰고, 색을 고를 때 대비를 잰다. 개발자에게 넘길 때 이것들이 빠져 있으면 개발자가 추측으로 채우거나 빼먹는다.
만든 뒤의 점검은 세 겹으로 한다.
- 자동 검사. 브라우저 개발자 도구의 Lighthouse, axe 같은 검사기는 대비 부족, alt 속성 누락, 라벨 없는 칸, 언어 미지정처럼 기계가 판정할 수 있는 문제를 빠르게 찾는다. 하지만 대체 텍스트가 알맞은지, 포커스 순서가 말이 되는지, 제목이 내용을 잘 요약하는지는 판정하지 못한다. 자동 검사로 찾을 수 있는 것은 전체 문제의 일부라는 것이 실무자들 사이의 통설이다. 점수가 만점이어도 쓸 수 없는 페이지가 있다.
- 사람의 점검. 키보드만으로 핵심 일을 끝까지 해 보기, 화면 낭독기로 제목·링크 목록 듣기, 200%·400% 확대, 흑백으로 보기. 이 장의 시뮬레이터가 각각 하나씩이다.
- 사용자에게 묻기. 실제로 보조 기술을 쓰는 사람이 써 보는 것만큼 정확한 점검은 없다. 한 사람이라도 좋다.
서윤의 웹 개편 노트
"사이트는 예쁜데, 저한테는 비어 있었어요"
2월 셋째 주, 시험 주소로 새 사이트를 열어 둔 지 일주일이 지났다. 책방 E 주인이 낭독회 단골 손님 한 분의 말을 전했다. 시각장애가 있어 휴대폰의 화면 낭독기로 웹을 쓰는 손님이었다. 낭독회 모임을 직접 신청해 보려다 그만두었다고 했다. 서윤과 지후는 손님에게 양해를 구하고, 토요일 오후 서점 2층에서 손님이 신청하는 과정을 옆에서 들었다.
첫 화면에서 손님은 제목으로 건너뛰는 동작부터 했다. 낭독기는 "제목 없음"이라고 답했다. 지후가 시안의 큰 글자를 모두 굵은 div로 짜 두었기 때문이다. 다음 모임 사진은 "img_0231 점 jpg"로, 로고 링크는 파일 이름으로 읽혔다. 신청 폼에 들어가서는 날짜를 고를 수 없었다. 날짜 칩이 div라 낭독기의 이동에도, 키보드에도 걸리지 않았다. 손님이 일부러 연락처를 짧게 넣고 신청하기를 눌러 보았지만, 칸 테두리가 빨갛게 바뀐 것 말고는 아무 소리도 나지 않았다. "사이트는 예쁘다고 들었는데, 저한테는 비어 있었어요."
그날 밤 지후는 노트북의 키보드만으로 같은 신청을 해 보았다. 7장에서 정한 버튼 포커스 상태는 있었지만, 칩과 링크에는 누군가 넣은 outline: none이 그대로 남아 있었고, 하단의 "3월 1일 휴무" 안내 띠가 신청하기 버튼을 덮었다. 한도경은 휴대폰 글자를 가장 크게 한 채 노트북 브라우저도 200%로 키워 보더니, 행사 카드의 날짜 줄이 상자 밖으로 잘린다고 사진을 찍어 보냈다. 카드 높이를 px로 고정해 둔 탓이었다.
서윤의 선택은 "고치기"가 아니라 "점검표 만들기"였다. 한 번 고치고 끝내면 다음 페이지에서 같은 일이 생긴다. 시안 단계에서 채울 칸과 공개 전에 확인할 칸을 나누어, 이후 모든 페이지가 이 표를 통과하게 했다. 손님은 공개 전 마지막 점검에 한 번 더 와 주기로 했다.
| 점검 항목 | 서윤의 기준 | 관련 기준 |
|---|---|---|
| 제목 구조 | 페이지마다 주제를 말하는 h1 하나, 단계 건너뛰지 않기, header·nav·main·footer 랜드마크, 첫 Tab에 "본문 바로가기" | 1.3.1, 2.4.1, 2.4.6 |
| 대체 텍스트 | 정보 사진은 한두 문장으로 "무엇이, 왜", 링크·단추 그림은 목적지·동작, 꾸밈은 alt="", 포스터 속 날짜·시간은 본문 글자로도 | 1.1.1 |
| 키보드만으로 신청 | 모든 조작 요소는 button·a·폼 요소로, 양수 tabindex 금지, 포커스 순서 = 읽는 순서, :focus-visible에 7장의 3px 짙은 링(#22342a, 어두운 영역 안에서는 크림 #ffe2d7)과 2px 띄우기, 고정 띠 높이만큼 scroll-padding | 2.1.1, 2.4.3, 2.4.7, 2.4.11 |
| 폼 오류 | 오류는 글자로("오류:"로 시작, 고칠 방법 포함), aria-invalid·aria-describedby로 칸에 연결, 제출 때 첫 오류 칸으로 포커스, 접수 알림은 상태 영역에 | 1.4.1, 3.3.1, 4.1.3 |
| 대비 | 글자 4.5:1, 큰 글자·입력 칸 테두리·포커스 표시 3:1 이상. 안내 글자는 6장의 text-muted #567666(흰 칸 5.03:1, 바탕 4.83:1), 입력 칸 테두리도 #567666(5.03:1) | 1.4.3, 1.4.11 |
| 200% 확대 | 카드·버튼 높이를 px로 고정하지 않기, 200%와 320px 폭에서 가로 스크롤 없음 | 1.4.4, 1.4.10 |
outline: 3px solid #22342a; outline-offset: 2px(7장의 링, 흰 바탕 13.19:1, 크림 #ffe2d7 바탕 10.74:1), 짙은 바닥글 안에서는 #ffe2d7 링(10.74:1). 안내 글자와 입력 칸 테두리는 #567666. 공개 전에는 키보드만으로 신청 한 번, 휴대폰 낭독기로 제목·링크 목록 듣기 한 번, 200% 확대 한 번을 반드시 하고, 자동 검사 점수는 참고로만 본다.핵심 정리
- 웹 접근성은 보지 않고 듣는 사람, 마우스 없이 쓰는 사람, 크게 키워 보는 사람, 잠시 제약이 생긴 사람 모두가 웹을 쓸 수 있게 하는 것이다. 국제 기준은 WCAG 2.2(2023), 국내 기준은 KWCAG 2.2이며 항목 번호는 서로 다르다.
- 보조 기술은 픽셀이 아니라 접근성 트리(역할·이름·상태)를 읽는다. 그 일을 하는 HTML 요소(
button,a,label, 제목, 랜드마크)를 쓰면 트리가 저절로 바르게 선다. ARIA는 HTML로 안 될 때만 보탠다. - 제목 요소는 크기가 아니라 구조를 적는다. 페이지마다
h1하나, 단계 건너뛰지 않기. 링크 문구는 그것만 들어도 목적지를 알게 쓴다(2.4.4). - 키보드로 모든 기능을 쓸 수 있어야 하고(2.1.1), 순서는 읽는 순서를 따르며(2.4.3), 포커스는 보여야 하고(2.4.7), 고정된 띠에 완전히 가려져서는 안 된다(2.4.11).
outline: none과 양수tabindex가 흔한 원인이다. - 대체 텍스트는 그림이 그 자리에서 하는 일을 쓴다. 꾸밈은
alt="", 링크 안의 그림은 목적지, 그림 속 글자 정보는 본문 글자로도 둔다(1.1.1). - 폼 오류는 색이 아니라 글자로, 칸에 연결해 알린다(1.4.1, 3.3.1). 본문 글자 4.5:1, 큰 글자와 UI 요소 3:1 대비(1.4.3, 1.4.11).
- 확대는 좁은 화면과 같다. 200%에서 잃는 것이 없고(1.4.4), 320 CSS px 폭에서 가로 스크롤 없이 읽혀야 한다(1.4.10). 반응형 레이아웃과 높이를 고정하지 않는 습관이 답이다.
확인 퀴즈
1. WCAG 2.2에서 새로 생긴 2.4.11 가려지지 않는 포커스(최소)가 요구하는 것은?
2. 행사 소개 글 사이에 놓인 꽃잎 모양 구분선 그림의 대체 텍스트로 가장 알맞은 것은?
alt=""로 비워 보조 기술이 무시하게 한다(1.1.1). 이름을 붙이면 듣는 사람에게 쓸모없는 소리가 늘고, alt 속성을 빼면 많은 낭독기가 대신 파일 이름을 읽는다. CSS 배경으로 넣는 것도 방법이다.3. 시안의 큰 제목을 모두 <div class="title">로 짜고 CSS로 크고 굵게 꾸몄다. 눈으로 보면 문제가 없다. 화면 낭독기 사용자에게 생기는 가장 직접적인 문제는?
div 안의 글자는 그냥 글로 읽히지만, 제목이라는 역할이 접근성 트리에 없다. 그래서 제목 목록에 나오지 않고 "다음 제목" 이동에도 걸리지 않는다(1.3.1). 대비는 화면 낭독기와 상관이 없고, 제목은 원래 키보드 포커스를 받는 요소가 아니다.4. 1.4.10 리플로 기준을 가장 정확하게 설명한 것은?