웹 퍼블리셔로 일하다 보면 한 번쯤 이런 말을 듣는다.
"여기 웹 접근성도 맞춰주세요."

처음에는 솔직히 alt만 잘 넣으면 되는 줄 알았다.
이미지에 대체 텍스트 넣고, 버튼에 aria-label 조금 붙이고, 시맨틱 태그 신경 쓰면 되는 정도라고 생각했다.
지금은 생각이 많이 달라졌다.
접근성에서 내가 가장 많이 배운 건 지침 문서가 아니라,
화면상으로는 아무 문제가 없는데 Tab을 눌러보니 문제가 있었던 순간들이었다.
이 글은 웹 접근성 체크리스트가 아니다.
실제 프로젝트를 하면서 걸렸던 문제와, 그걸 겪고 나서 내 작업 방식이 어떻게 바뀌었는지에 대한 이야기다.

병원 사이트의 한 섹션에 Swiper를 사용했다.
슬라이드마다 이미지 각도를 변경하는
정면 / 45도 / 측면
버튼이 세 개씩 있는 구조였다.
마우스로 테스트했을 때는 아무 문제가 없었다.
슬라이드는 잘 넘어갔고 버튼도 정상적으로 눌렸다.
그런데 Tab 키를 눌러봤다.
정면.
45도.
측면.
그리고 다시 정면.
45도.
측면.
계속 눌러도 끝이 안 났다.
원인은 loop: true였다.
Swiper에서 loop를 사용하면 슬라이드를 자연스럽게 반복하기 위해 내부적으로 슬라이드가 반복되는 구조가 만들어진다.
문제는 화면에는 보이지 않는 슬라이드 안의 버튼까지 Tab 순서에 포함될 수 있다는 것이다.
마우스로만 테스트했다면 아마 끝까지 몰랐을 문제다.
화면을 보고 사용하는 사람에게는 슬라이드 하나지만, 키보드 사용자에게는 같은 버튼을 몇 번씩 반복해서 지나가야 하는 구조가 되어 있었다.
이런 경우 화면에 노출되지 않는 슬라이드의 인터랙션 요소를 탭 순서에서 제외해야 한다.
<div class="swiper-slide" aria-hidden="true">
<button type="button" tabindex="-1">
정면
</button>
</div>
여기서 aria-hidden="true"만 넣는 것도 충분하지 않다.
aria-hidden은 보조기술에서 해당 영역을 숨기는 역할을 하지만, 내부 요소가 원래 포커스 가능한 요소라면 키보드 포커스 문제는 별도로 처리해야 한다.
그래서 상황에 따라 내부 버튼이나 링크의 Tab 진입도 함께 제어해야 한다.
이 일을 겪고 나서 습관이 하나 생겼다.
UI를 다 만들면 마우스에서 손을 떼고 Tab부터 눌러본다.
다른 사이트에서는 Tab 이동 자체는 잘 됐다.
그런데 이상했다.
Tab을 누를 때마다 뭔가 이동은 하는 것 같은데,
지금 내가 어디에 있는지 전혀 보이지 않았다.
원인은 아주 익숙한 코드였다.
input,
button {
outline: none;
}
디자인상 기본 포커스 테두리가 보기 싫어서 리셋 CSS에 습관적으로 넣는 코드다.
나도 그렇게 써왔다.
문제는 그 outline이 키보드 사용자에게는 일종의 현재 위치 표시라는 것이다.
그걸 없애버리면 Tab으로 이동은 가능해도 현재 어느 버튼을 선택하고 있는지 알 수가 없다.
없애고 싶다면 대신 보여줘야 한다.
button:focus-visible {
outline: 3px solid #005fcc;
outline-offset: 3px;
}
focus-visible을 사용하면 일반적인 마우스 클릭과 키보드 포커스를 어느 정도 구분해서 스타일을 적용할 수 있다.
그래서 기본 포커스 디자인이 UI를 해친다는 이유로 무조건 제거하는 것보다, 서비스 디자인에 맞는 포커스 스타일을 새로 만들어주는 편이 낫다.
특히 리셋 CSS는 프로젝트마다 그대로 복사해서 사용하는 경우가 많다.
한 번 들어간 코드가 몇 년 동안 살아남기도 한다.
그래서 지금은 새 프로젝트를 열면 CSS에서 outline부터 한 번 검색해본다.
한 프로젝트에서는 일부 요소를 수정하는 수준이 아니라 사이트 전반의 접근성을 다시 살펴봐야 했다.
이때부터 접근성을 조금 다르게 보게 됐다.
페이지에 들어와 Tab을 처음 눌렀을 때 바로 본문으로 이동할 수 있는 링크다.
평소에는 보이지 않다가 키보드 포커스를 받으면 나타난다.
이게 없으면 키보드 사용자는 페이지가 바뀔 때마다 GNB 전체를 계속 지나가야 한다.
마우스로 사이트를 사용할 때는 존재하는지도 모르는 기능인데, 키보드 사용자에게는 꽤 중요하다.
마우스 hover를 기준으로 만들어진 메뉴를 키보드로도 사용할 수 있게 수정했다.
현재 페이지를 나타내는 메뉴에는 aria-current를 적용했다.
접근성을 작업하다 보면 자주 느끼는 게 있다.
화면에서는 당연히 보이는 정보가 화면을 보지 않는 사용자에게는 전혀 전달되지 않을 수 있다는 것.
현재 메뉴도 그중 하나였다.
검수하다가 직접 잡은 문제도 있었다.
GNB의 Tab 순서는 거의 정리됐는데 햄버거 메뉴에 포커스가 갔을 때 문제가 있었다.
클릭하면 메뉴가 열리는데 키보드 탐색 흐름에서는 제대로 동작하지 않았다.
그리고 메뉴가 열렸다는 상태도 보조기술에 전달할 필요가 있었다.
<button
type="button"
aria-expanded="false"
aria-controls="gnb"
>
전체 메뉴
</button>
메뉴가 열렸다면 JavaScript에서 aria-expanded도 함께 변경한다.
button.setAttribute('aria-expanded', 'true');
화면을 보면 메뉴가 열렸다는 게 너무나 명확하다.
하지만 스크린리더 사용자는 상태 정보를 전달받지 못하면 아무 변화가 없었던 것과 같다.
사이드 메뉴가 열렸는데 Tab을 계속 누르다 보니 포커스가 메뉴 밖으로 빠져나갔다.
화면에는 메뉴가 전체를 덮고 있는데, 키보드는 뒤쪽 페이지를 돌아다니는 상태였다.
모달이나 전체 메뉴처럼 사용자의 작업 범위를 일시적으로 제한하는 UI에서는 포커스 흐름까지 같이 설계해야 한다.
열려 있는 동안에는 필요한 영역 안에서 탐색할 수 있게 하고,
닫았을 때는 메뉴를 열었던 버튼으로 포커스를 다시 돌려준다.
오시는 길에 들어가는 카카오맵도 그냥 지도를 화면에 띄우는 것으로 끝내지 않았다.
지도 영역의 목적을 알 수 있도록 접근성 정보를 추가하고,
지도를 직접 조작하기 어려운 상황에서도 위치를 확인할 수 있도록
같은 별도의 이동 경로도 함께 제공했다.
이 프로젝트를 하면서 알게 된 건,
마크업을 제대로 만드는 것만큼 실제 포커스가 어디로 이동하는지 확인하는 것도 중요하다는 것이었다.
여러 프로젝트에서 팝업과 전체 메뉴를 만들면서 알게 된 게 있다.
팝업은 display: block으로 띄웠다고 끝나는 게 아니다.
최소한 흐름이 이 정도는 되어야 한다.
열기 버튼
→ 팝업 내부로 포커스 이동
→ 팝업 내부 탐색
→ ESC 또는 닫기 버튼으로 종료
→ 처음 팝업을 열었던 버튼으로 포커스 복귀
개인적으로 여기서 가장 쉽게 빠지는 건 마지막이다.
팝업은 잘 닫혔는데 포커스가 사라지거나 페이지의 엉뚱한 곳으로 이동하는 경우가 있다.
그러면 키보드 사용자는 방금 보고 있던 위치를 다시 찾아가야 한다.
최근에는 브라우저 기본 요소인 <dialog>도 사용할 수 있다.
<button id="openModal">
상세 보기
</button>
<dialog id="modal">
<button id="closeModal">
닫기
</button>
</dialog>
const modal = document.querySelector('#modal');
const openButton = document.querySelector('#openModal');
modal.addEventListener('close', () => {
openButton.focus();
});
접근성 작업을 하면 자꾸 이런 질문을 하게 된다.
"그래서 이걸 사용하고 난 다음 사용자는 어디에 있지?"
UI를 열 때보다 닫았을 때가 중요한 이유다.
국내 웹 접근성을 확인할 때 기본적으로 보는 기준은 한국형 웹 콘텐츠 접근성 지침 2.2(KWCAG 2.2)다.
KWCAG 2.2는
으로 구성되어 있다.
국제 기준으로는 WCAG 2.2가 있다.
WCAG 2.2는 2023년 10월 W3C Recommendation으로 발표됐고, WCAG 2.1에서 새로운 성공 기준들이 추가됐다.
이름은 둘 다 2.2라 처음 보면 헷갈리는데 같은 문서는 아니다.
국내 프로젝트라면 KWCAG를 기본으로 보고, 글로벌 서비스나 보다 넓은 접근성 기준이 필요한 경우 WCAG도 함께 참고하면 된다.
WCAG 2.2를 다시 살펴보다가 개인적으로 눈에 들어온 항목도 있었다.
포커스를 받은 요소가 고정 헤더나 플로팅 UI 등에 가려져서는 안 된다는 내용이다.
이걸 보고 조금 뜨끔했다.
상단 고정 헤더와 하단 상담 플로팅 버튼은 병원 사이트에서 정말 자주 사용한다.
그런데 Tab으로 페이지 전체를 이동하면서 현재 포커스된 요소가 플로팅 UI 밑에 가려지는지까지 따로 확인했던 적은 많지 않았다.
버튼이나 링크처럼 직접 클릭하거나 터치해야 하는 영역의 크기와 관련된 기준이다.
디자인 시안에서 아이콘이 16px이라고 해서 버튼까지 16px일 필요는 없다.
.close-button {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
}
아이콘은 작아도 클릭 영역은 충분히 확보할 수 있다.
모바일에서는 접근성이 곧 사용성이 되는 경우가 많다.
로그인과 인증 과정도 접근성의 영역이다.
예를 들어 보안을 이유로 비밀번호 복사·붙여넣기를 무조건 막으면 비밀번호 관리자 등을 사용하는 사람에게 오히려 불편한 인증 과정이 될 수 있다.
웹 접근성이 단순히 alt, label, ARIA의 문제가 아니라는 걸 보여주는 항목이라고 생각한다.
예전에는 접근성을 이렇게 생각했다.
"사이트 다 만든 다음 검사해서 몇 개 고치는 작업."
지금은 반대로 생각한다.
HTML 구조부터 잘못 만들었다면 마지막에 접근성을 맞추기가 어렵다.
버튼 역할의 UI를 div로 만들어놨다면 키보드 이벤트를 추가해야 하고,
팝업 구조가 잘못되어 있다면 포커스 로직부터 다시 만들어야 한다.
결국 가장 비용이 적게 드는 방법은 만들 때 같이 챙기는 것이었다.
그래서 요즘 작업 순서는 대략 이렇다.
button, 링크는 a를 먼저 사용한다. ARIA를 붙이기 전에 HTML부터 맞춘다.여전히 모르는 게 많다.
WCAG를 다시 읽으면서 새롭게 알게 되는 기준도 계속 있다.
그래도 하나는 확실하게 배웠다.
화면이 정상이라고 접근성이 정상인 건 아니다.
그리고 의외로 그걸 확인하는 가장 간단한 방법은 거창한 검사 도구가 아니라,
마우스에서 손을 떼고
Tab 키를 한 번 눌러보는 것이었다.