iBetter Books
수정

도입 스토리

"마우스를 뺄게요." 박멘토가 마우스를 서랍 속에 넣었습니다.

"Tab 키만으로 회원가입까지 완료해봐요."

김개발이 Tab을 눌렀습니다. 포커스가 메뉴로 이동했습니다. 다시 Tab. 포커스가... 사라졌습니다. 어디에 있는지 전혀 보이지 않았습니다.

"포커스가 어디 있는지 안 보여요."

"맞아요. outline: none 처리를 해놨거든요." 박멘토가 말했습니다. "키보드 사용자는 지금 이 페이지를 쓸 수 없어요. 운동 장애가 있어서 마우스를 못 쓰는 분들, 손이 부상 중인 분들, 파워 유저들까지 전부요."

핵심 개념 설명

키보드 접근성의 기본

WCAG 성공 기준 2.1.1은 마우스로 할 수 있는 모든 기능은 키보드로도 가능해야 한다고 명시합니다. 단, 드로잉처럼 경로 자체가 중요한 경우는 예외입니다.

기본 키보드 탐색 키:

  • Tab: 포커스를 다음 인터랙티브 요소로 이동
  • Shift+Tab: 이전 요소로 이동
  • Enter / Space: 활성화 (버튼, 링크, 체크박스 등)
  • 방향키: 라디오 버튼, 메뉴, 슬라이더 등 그룹 내 이동
  • Escape: 모달, 드롭다운 닫기

포커스 가시성 — WCAG 2.4.7

포커스가 어디 있는지 반드시 보여야 합니다.

/* 절대 하지 말 것 */* {  outline: none; /* 키보드 사용자의 시야를 빼앗음 */}/* 올바른 포커스 스타일 */:focus-visible {  outline: 3px solid #005FCC;  outline-offset: 2px;  border-radius: 2px;}

:focus-visible은 키보드 탐색 시에만 포커스 링을 표시하고, 마우스 클릭 시에는 숨깁니다. 디자인과 접근성을 동시에 만족합니다.

포커스 순서 — WCAG 2.4.3

Tab 이동 순서가 논리적이어야 합니다. 일반적으로 DOM 순서를 따릅니다.

<!-- 잘못된 예: tabindex가 시각적 순서를 망가뜨림 --><button tabindex="3">세 번째</button><button tabindex="1">첫 번째</button><button tabindex="2">두 번째</button><!-- 올바른 예: DOM 순서가 곧 Tab 순서 --><button>첫 번째</button><button>두 번째</button><button>세 번째</button>

tabindex="0"은 기본 Tab 순서에 요소를 포함시킵니다. tabindex="-1"은 Tab으로 접근은 안 되지만 JavaScript로 포커스를 보낼 수 있습니다.

// tabindex="-1" 요소에 프로그래밍 방식으로 포커스 보내기document.getElementById('modal-title').focus();

포커스 함정 — WCAG 2.1.2

모달 창 안에서 Tab을 누르면 모달 안에서만 이동해야 합니다. 배경 페이지로 빠져나가면 안 됩니다. 이를 포커스 트랩(Focus Trap)이라고 합니다.

function trapFocus(element) {  const focusableElements = element.querySelectorAll(    'a[href], button:not([disabled]), input, select, textarea, [tabindex]:not([tabindex="-1"])'  );  const firstElement = focusableElements[0];  const lastElement = focusableElements[focusableElements.length - 1];  element.addEventListener('keydown', (e) => {    if (e.key !== 'Tab') return;    if (e.shiftKey) {      // Shift+Tab: 첫 번째 요소에서 마지막으로      if (document.activeElement === firstElement) {        e.preventDefault();        lastElement.focus();      }    } else {      // Tab: 마지막 요소에서 첫 번째로      if (document.activeElement === lastElement) {        e.preventDefault();        firstElement.focus();      }    }  });}

커스텀 컴포넌트의 키보드 지원

<div>, <span>으로 만든 버튼에는 기본 키보드 동작이 없습니다.

<!-- 잘못된 예: div 버튼은 키보드 접근 불가 --><div class="btn" onclick="submit()">제출</div><!-- 방법 1: 시맨틱 요소 사용 (권장) --><button type="button" onclick="submit()">제출</button><!-- 방법 2: role과 tabindex로 보완 --><div  role="button"  tabindex="0"  onclick="submit()"  onkeydown="if(event.key==='Enter'||event.key===' ')submit()">  제출</div>

가능하면 <button>, <a>, <input> 같은 네이티브 요소를 사용하는 것이 훨씬 간단하고 안전합니다.

단계별 실습

따라하기: 키보드만으로 사이트 탐색하기

마우스를 치우고 Tab 키만으로 웹사이트를 탐색합니다.

체크리스트:

  • 포커스 위치가 항상 보이는가.
  • 모든 링크와 버튼에 Tab으로 접근 가능한가.
  • Tab 순서가 왼쪽에서 오른쪽, 위에서 아래로 논리적인가.
  • 모달을 열었을 때 포커스가 모달 안으로 이동하는가.
  • Escape로 모달을 닫을 수 있는가.
  • 모달을 닫으면 포커스가 열었던 버튼으로 돌아오는가.

변형하기: 포커스 스타일 개선하기

현재 프로젝트의 포커스 스타일을 찾아 개선합니다.

/* Chrome DevTools에서 포커스 강제 표시 *//* Elements 탭 → :hov → :focus 체크 *//* 브랜드 색상을 활용한 포커스 스타일 */:focus-visible {  outline: 3px solid var(--color-primary, #005FCC);  outline-offset: 3px;  border-radius: 4px;}/* 어두운 배경 위 포커스 */.dark-background :focus-visible {  outline-color: #ffffff;}

응용하기: axe로 키보드 접근성 검사

axe DevTools에서 다음 규칙을 확인합니다.

  • scrollable-region-focusable: 스크롤 영역 포커스 가능 여부
  • focus-order-semantics: 포커스 순서 의미론적 정확성
  • tabindex: tabindex 과다 사용 여부

정리와 확인

핵심 내용 요약

  • WCAG 2.1.1: 모든 기능은 키보드로 사용 가능해야 함
  • :focus-visible: 키보드 탐색 시 포커스 링 표시, 마우스 클릭 시 숨김
  • DOM 순서 = Tab 순서: tabindex 값 남용 금지
  • 포커스 트랩: 모달 안에서 Tab이 순환해야 함
  • 네이티브 요소 우선: <div> 버튼보다 <button> 사용

확인 문제

문제 1. 모달을 열었을 때 포커스를 모달 안으로 이동시키는 코드는?

// 모달의 첫 번째 포커스 가능 요소로 포커스 이동document.querySelector('#modal button, #modal [href], #modal input').focus();

문제 2. tabindex="2"를 사용하면 왜 문제가 될 수 있는가?

양수 tabindex는 Tab 순서를 강제 변경하여
DOM 순서와 시각적 순서가 불일치할 때 예측 불가능한 탐색 흐름을 만듭니다.
유지보수 중 다른 요소를 추가하면 순서가 더 복잡해집니다.

다음 챕터에서는 시간 제한과 자동 재생 콘텐츠의 접근성을 다룹니다. 카운트다운 타이머, 자동 슬라이더 — 서두르지 않아도 되는 웹을 만드는 방법을 알아봅니다.