키보드 접근성: 웹사이트를 완전히 키보드로 탐색 가능하게 만드는 방법

키보드 접근성은 웹 접근성에서 가장 중요하면서도 가장 간과되는 측면 중 하나로, 연구에 따르면 여전히 85%의 웹사이트가 적절한 키보드 내비게이션을 제공하지 못하고 있다. 이 가이드는 WCAG 요구사항, 일반적인 실패 패턴, 그리고 개발자와 컴플라이언스 담당자가 진정으로 키보드로 탐색 가능한 경험을 구축하는 데 도움이 되는 실질적인 코드 수준 기법을 다룬다.

직업 지원서를 작성하거나, 병원 진료를 예약하거나, 온라인 결제를 완료하려고 하는데 — 마우스가 전혀 작동하지 않는 상황을 떠올려 보세요. 고장 난 것이 아니라, 아예 쓸모가 없는 겁니다. Tab, Enter, 방향키만으로 모든 탐색을 해야 합니다. 전 세계 수백만 명에게 이것은 단순한 가정이 아닙니다. 운동 장애, 반복성 스트레스 손상, 시각 장애가 있는 사람들, 그리고 스크린 리더에 의존하는 사람들은 모두 키보드 내비게이션을 웹과 상호작용하는 기본 인터페이스로 사용합니다. 그런데 연구에 따르면 웹사이트의 85%가 적절한 키보드 내비게이션을 제공하지 못하고 있어, 이 사용자들을 매일 기본적인 디지털 작업에서 배제하고 있습니다. 만약 당신의 사이트가 이 다수에 속한다면, 이 가이드는 당신을 위한 것입니다.

누가 키보드 내비게이션에 의존하는가 — 그리고 왜 생각보다 훨씬 중요한가

키보드 접근성은 소수 사용자만을 위한 특수한 이슈가 아닙니다. 여기에 의존하는 사람들의 범위는 대부분의 개발자가 생각하는 것보다 훨씬 넓고 다양합니다. 마우스를 사용할 수 없는 신체 장애가 있는 사람들, 화면의 마우스 포인터를 볼 수 없는 시각 장애인, 그리고 반복성 스트레스 손상과 같은 만성 질환으로 마우스 사용을 줄여야 하는 사람들은 모두 키보드 내비게이션을 웹으로 가는 관문으로 사용합니다. 장애와 무관하게, 많은 파워 유저 — 개발자, 작가, 데이터 입력 담당자 — 역시 속도와 효율성을 위해 키보드 단축키를 선호합니다.

스크린 리더 사용자도 또 하나의 큰 집단입니다. 스크린 리더는 포커스와 시맨틱 구조를 기반으로 페이지 요소를 해석하고 읽어 주며, 사용자는 키보드 입력으로 콘텐츠를 이동합니다. 웹사이트가 키보드 포커스를 유지하지 못하거나 논리적인 포커스 순서를 지원하지 않으면, 스크린 리더 내비게이션은 완전히 무너집니다. Dragon NaturallySpeaking 같은 음성 인식 소프트웨어도 키보드 이벤트를 생성하기 때문에, 키보드 지원이 부족하면 음성으로 제어하는 브라우징 역시 깨지게 됩니다.

비즈니스 관점에서도 설득력이 충분합니다. 이용 가능한 데이터에 따르면, 미국의 장애인은 거의 5천억 달러에 달하는 가처분 소득을 보유하고 있습니다. 웹사이트가 키보드 접근성을 제공하지 않으면, 그 시장의 상당 부분을 스스로 내쫓는 셈입니다. 법적 리스크도 현실적입니다. 키보드 접근성은 WCAG 준수에 필수적이며, WCAG는 ADA, Section 508, European Accessibility Act, EN 301 549 준수의 기준점입니다. 사용자가 페이지 요소 안에 갇혀 빠져나올 수 없는 키보드 트랩만으로도 명백한 WCAG 실패로 간주되며, 실제 접근성 소송에서 자주 언급됩니다.

아마 가장 의미심장한 사실은 이것일 것입니다. 장애인의 71%는 사용하기 어렵다고 느끼는 웹사이트를 그냥 떠나 버립니다. 불평하지 않습니다. 떠납니다. 그리고 키보드 접근성 문제는 대개 가장 중요한 상호작용 지점 — 폼, 모달, 내비게이션 메뉴, 결제 플로우 — 주변에 집중되기 때문에, 그 피해는 그대로 전환율에 직격탄을 날립니다.

WCAG 프레임워크: 규칙이 실제로 요구하는 것

웹 콘텐츠 접근성 가이드라인(WCAG)은 키보드 관련 요구사항을 주로 지침 2.1 — “모든 기능을 키보드로 사용할 수 있게 할 것” 아래에 정리하고 있습니다. 무엇이 요구되고 무엇이 요구되지 않는지 이해하는 것이 준수의 첫 단계입니다. 2023년 10월 5일 공식 W3C 표준이 된 WCAG 2.2는 기존 프레임워크에 9개의 새로운 성공 기준을 추가했으며, 현재 ADA, Section 508, European Accessibility Act를 위한 권장 표준입니다.

알아두어야 할 핵심 키보드 관련 성공 기준은 다음과 같습니다.

  • SC 2.1.1 Keyboard (Level A): 모든 기능은 개별 키 입력에 특정한 타이밍을 요구하지 않는 키보드 인터페이스를 통해 조작 가능해야 합니다. 단, 기본 기능이 경로 의존 입력(예: 자유 손그림)을 요구하는 경우는 예외입니다. 이것이 기본선입니다 — 모든 인터랙티브 요소는 키보드로 도달 가능하고 조작 가능해야 합니다.
  • SC 2.1.2 No Keyboard Trap (Level A): 키보드로 포커스를 컴포넌트로 이동할 수 있다면, 키보드만으로 포커스를 그 컴포넌트에서 벗어나게 할 수도 있어야 합니다. 표준이 아닌 방법으로만 빠져나올 수 있다면, 그 방법을 사용자에게 알려야 합니다. 키보드 트랩은 키보드 사용자에게 절대적인 차단 요소입니다.
  • SC 2.4.3 Focus Order (Level A): 웹 페이지를 순차적으로 탐색할 수 있는 경우, 포커스 순서는 의미와 조작 가능성을 보존해야 합니다. 논리적이고 예측 가능한 Tab 순서는 타협할 수 없는 요구사항입니다.
  • SC 2.4.7 Focus Visible (Level AA): 키보드로 조작 가능한 모든 사용자 인터페이스는 키보드 포커스 표시기가 보이는 모드를 가져야 합니다. 사용자는 항상 자신이 페이지의 어디에 있는지 볼 수 있어야 합니다.
  • SC 2.4.11 Focus Not Obscured (Minimum) (Level AA — WCAG 2.2의 신규 항목): 키보드 포커스를 받을 수 있는 요소가 포커스를 받았을 때, sticky 헤더나 쿠키 배너 같은 작성자 생성 콘텐츠에 의해 완전히 가려져서는 안 됩니다.
  • SC 2.4.12 Focus Not Obscured (Enhanced) (Level AAA): 더 엄격한 버전으로, 포커스를 받은 컴포넌트의 어떤 부분도 작성자 생성 콘텐츠에 의해 가려져서는 안 됩니다.
  • SC 2.5.8 Target Size (Minimum) (Level AA — WCAG 2.2의 신규 항목): 인터랙티브 타깃은 최소 24x24 CSS 픽셀이어야 하며, 이는 운동 제어가 제한된 사용자의 오류를 줄여 줍니다.
  • SC 2.5.7 Dragging Movements (Level AA — WCAG 2.2의 신규 항목): 드래그를 요구하는 모든 기능은 단일 포인터 대안을 제공해야 합니다 — 이는 드래그 동작을 수행할 수 없는 키보드 사용자에게도 도움이 됩니다.

WCAG 2.2는 WCAG 2.1과 완전히 하위 호환됩니다 — 기준을 추가했을 뿐 제거하지 않았습니다(이제 폐기된 4.1.1 Parsing 제외). 이미 WCAG 2.1 AA를 충족하는 사이트라면, 새로 추가된 6개의 Level AA 기준만 구현하면 됩니다. 키보드 접근성 측면에서 특히 중요한 새로운 요구사항은, 포커스를 받은 요소가 sticky 페이지 요소에 의해 완전히 가려지지 않도록 하는 것입니다 — 이는 WCAG 2.1이 명시적으로 다루지 않았던, 현실에서 매우 흔한 문제입니다.

WCAG의 원칙은 말로는 단순하지만 구현은 까다롭습니다. 모든 기능을 키보드로 수행할 수 있다면, 키보드 사용자, 음성 입력, 온스크린 키보드, 그리고 시뮬레이션된 키 입력을 생성하는 다양한 보조 기술로도 수행할 수 있습니다. 이 정도의 유연성과 보편적 지원을 가진 입력 방식은 다른 어떤 것도 없습니다.

가장 흔한 키보드 접근성 실패(그리고 그 원인)

수동 감사 결과를 보면, 키보드 접근성 문제는 웹에서 가장 흔하고 파괴적인 접근성 장벽 중 하나입니다. 한 대규모 연구에서, 폼이 있는 페이지의 54%가 키보드 접근성 문제를 가지고 있었으며, 이는 폼 필드 간 Tab 이동, 팝업 창 닫기, Submit 버튼 누르기 같은 핵심 사용자 행동에 영향을 미쳤습니다. 테스터들은 종종 장바구니를 포기하거나, 키보드만으로 제어할 수 없는 요소에 갇힌 뒤 페이지를 새로고침해야 했습니다.

근본 원인은 몇 가지 반복되는 패턴에 집중되어 있으며, 이를 자세히 살펴볼 가치가 있습니다.

1. 마우스 전용 이벤트 핸들러. <div> 요소에 대해 동등한 키보드 이벤트 핸들러 없이 onmouseover, onmouseout, onclick을 사용하는 것은 가장 흔한 실패 중 하나입니다. 클릭 핸들러가 있는 <div>는 버튼이 아닙니다 — 암시적인 키보드 역할이 없고, Tab으로 포커스를 받을 수 없으며, Enter나 Space에도 반응하지 않습니다. ARIA로 role='button'을 부여하면 스크린 리더에는 도움이 되지만, 여전히 tabindex='0', onkeydown, onkeyup 핸들러를 수동으로 추가해야 합니다. 올바른 해결책은 거의 항상 진짜 <button> 요소를 사용하는 것입니다.

2. 포커스 아웃라인 제거. 가장 널리 퍼진 문제 중 하나는 CSS에서 전역 또는 포커스된 요소에 outline: none 또는 outline: 0을 적용하는 것입니다. 디자이너는 특정 테마에서 보기 좋지 않다는 이유로 브라우저의 기본 포커스 링을 제거하곤 합니다. 그 결과, 키보드 사용자는 자신이 페이지의 어디에 있는지 전혀 알 수 없게 됩니다. 이는 WCAG SC 2.4.7을 직접적으로 위반하는 것이며, 만들기도 고치기도 가장 쉬운 문제 중 하나입니다.

3. 모달, 위젯, iframe의 키보드 트랩. 포커스를 제대로 제한하지 않는 모달 다이얼로그는 Tab이 모달을 지나 배경의 가려진 콘텐츠로 계속 이동하게 만들어, 키보드로는 모달을 닫을 수 없게 만듭니다. 서드파티 위젯 — 채팅 도구, 비디오 플레이어, 날짜 선택기, 지도 임베드 — 은 내부 키보드 처리 로직이 불투명하기 때문에 특히 이런 문제에 취약합니다.

4. 비논리적인 Tab 순서. 기본 키보드 내비게이션 순서는 DOM 소스 순서에 의해 결정됩니다. 개발자가 CSS Grid, Flexbox, CSS 포지셔닝을 사용해 DOM 순서와 독립적으로 시각적 배치를 재정렬하면, Tab 포커스는 시각적 레이아웃과 전혀 맞지 않는 방식으로 화면 여기저기를 뛰어다니게 됩니다. 특정 순서를 강제로 만들기 위해 사용하는 양수 tabindex 값(예: tabindex='2')은 실제 환경에서는 이 문제를 훨씬 더 악화시키는 경우가 대부분입니다.

5. hover 전용 드롭다운 메뉴. 마우스 hover에서만 열리고 키보드 트리거가 없는 내비게이션 메뉴는 키보드 사용자를 고립시킵니다. 이는 CSS만으로 구현된 드롭다운 메뉴에서 매우 흔한 패턴으로, 서브메뉴가 :hover에서만 나타나고 포커스 기반 내비게이션에는 전혀 노출되지 않습니다.

6. 동적 상호작용 후 포커스 미복원. 모달, 드로어, 플라이아웃이 닫힐 때, 포커스는 이를 트리거한 요소로 돌아와야 합니다. 그렇지 않고 — 페이지 맨 위로 가거나 알 수 없는 위치로 사라져 버리면 — 사용자는 완전히 길을 잃습니다. 동적 싱글 페이지 애플리케이션은 특히 이런 문제에 취약합니다.

키보드 접근성을 제대로 구축하기: 실용적인 구현

실패 패턴을 염두에 두고, 일반적인 웹사이트에서 가장 중요한 영역별로 올바른 구현이 어떤 모습인지 살펴보겠습니다.

먼저 시맨틱 HTML을 사용하라

기본 HTML 요소는 기본적으로 키보드 접근성이 있습니다. 링크(<a href>), 버튼(<button>), 폼 입력, select, textarea는 모두 Tab 순서에 참여하고, 표준 키 입력에 반응하며, 보조 기술에 자신의 역할을 전달합니다 — 추가 JavaScript 한 줄 없이도 말입니다. <button> 요소는 자동으로 올바른 역할을 가지며, 키보드 접근성이 있고, Enter와 Space에 반응하며, 적절한 포커스 관리가 내장되어 있습니다. <div>role='button'을 추가하면 역할은 맞지만, 여전히 키보드 지원과 포커스 관리를 수동으로 구현해야 합니다. 항상 시맨틱 HTML을 우선하세요.

<!-- 피해야 할 예: 버튼인 척하는 비시맨틱 div -->
<div onclick='doSomething()' class='btn'>Submit</div>

<!-- 올바른 예: 네이티브 button 요소 -->
<button type='button' onclick='doSomething()'>Submit</button>

포커스 표시기를 고치라

브라우저의 기본 outline을 제거하는 대신, 스타일링된 커스텀 포커스 표시기로 대체하세요. WCAG 2.2 SC 2.4.11은 포커스 표시 영역이 포커스되지 않은 컴포넌트 둘레 2 CSS 픽셀 두께의 둘레와 최소한 동일한 크기여야 하며, 포커스된 상태와 포커스되지 않은 상태 간의 명도 대비가 최소 3:1이어야 한다고 요구합니다. :focus 대신 :focus-visible 의사 클래스를 사용해, 마우스 인터랙션의 미관을 해치지 않으면서 키보드 사용자에게만 포커스 표시기를 보여 주세요.

/* 기본 표시기를 제거하되 더 나은 표시기로 대체 */
*:focus {
  outline: none;
}

*:focus-visible {
  outline: 3px solid #005fcc;
  outline-offset: 3px;
  border-radius: 2px;
}

이 접근 방식은 WCAG를 준수하면서도 시각적 표현을 완전히 제어할 수 있게 해 줍니다. 특히 다크 테마 사이트나 이미지 위에서, 포커스 색상이 배경과 컴포넌트 자체 모두에 대해 충분한 대비를 가지도록 하세요.

동적 상호작용에서 포커스를 관리하라

콘텐츠가 동적으로 변경될 때 — 모달을 열거나, 새 콘텐츠를 로드하거나, 요소를 제거할 때 — 포커스를 프로그래밍 방식으로 관리해야 합니다. 모달을 열 때는 포커스를 모달 내부의 첫 번째 포커스 가능 요소로 이동시키세요. 닫을 때는 트리거 요소로 포커스를 되돌립니다. 이를 위해 JavaScript의 .focus() 메서드를 사용합니다. 모달 안에 포커스를 올바르게 가두려면, Tab과 Shift+Tab 키 이벤트를 가로채서 다이얼로그 내 첫 번째와 마지막 포커스 가능 요소 사이에서 포커스를 순환시키세요.

// 모달 열기: 포커스를 내부로 이동
function openModal(modalEl, triggerEl) {
  modalEl.removeAttribute('hidden');
  const firstFocusable = modalEl.querySelector(
    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
  );
  if (firstFocusable) firstFocusable.focus();
}

// 모달 닫기: 포커스를 트리거로 되돌리기
function closeModal(modalEl, triggerEl) {
  modalEl.setAttribute('hidden', '');
  triggerEl.focus();
}

Skip Navigation 링크를 구현하라

키보드 사용자는 매 페이지 로드마다, 메인 콘텐츠에 도달하기 전에 헤더, 내비게이션 메뉴, 검색창 등 모든 인터랙티브 요소를 Tab으로 통과해야 합니다. Skip 링크는 이에 대한 해결책입니다. 페이지 맨 위에 위치한, 시각적으로는 숨겨져 있지만 포커스를 받으면 보이게 되고, 사용자를 메인 콘텐츠 영역으로 바로 이동시키는 링크입니다. 이는 WCAG Level A 요구사항이며, 가장 영향력이 큰 빠른 개선 조치 중 하나입니다.

<!-- <body>의 첫 번째 요소로 배치 -->
<a href='#main-content' class='skip-link'>Skip to main content</a>

<!-- 메인 콘텐츠 컨테이너의 타깃 앵커 -->
<main id='main-content' tabindex='-1'>
  <!-- page content -->
</main>
/* 키보드 포커스일 때만 skip 링크 표시 */
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  background: #000;
  color: #fff;
  padding: 8px 16px;
  z-index: 100;
  transition: top 0.2s;
}

.skip-link:focus {
  top: 0;
}

접근 가능한 내비게이션 메뉴를 구축하라

드롭다운 서브메뉴가 있는 내비게이션 메뉴는 세심한 주의가 필요합니다. 내비게이션 메뉴에 대한 올바른 키보드 상호작용 패턴은 다음과 같습니다. Tab은 상위 항목 간을 이동하고, Enter 또는 Space는 서브메뉴를 열며, 방향키는 서브메뉴 내에서 이동하고, Escape는 서브메뉴를 닫고 포커스를 트리거로 되돌립니다. 상태를 전달하기 위해 ARIA 속성을 사용하세요. hover에서만 열리고 키보드 트리거가 없는 메뉴는 접근성이 없으며 반드시 수정해야 합니다.

<nav aria-label='Main navigation'>
  <ul role='menubar'>
    <li role='none'>
      <button
        aria-haspopup='true'
        aria-expanded='false'
        aria-controls='products-menu'>
        Products
      </button>
      <ul role='menu' id='products-menu' hidden>
        <li role='none'>
          <a role='menuitem' href='/software'>Software</a>
        </li>
        <li role='none'>
          <a role='menuitem' href='/hardware'>Hardware</a>
        </li>
      </ul>
    </li>
  </ul>
</nav>

JavaScript로 aria-expanded='false'aria-expanded='true'를 토글해 열림/닫힘 상태를 전달하세요. aria-haspopup='true'는 버튼을 활성화하면 서브메뉴가 열린다는 것을 알립니다. Escape 키가 서브메뉴를 닫고 포커스를 버튼 트리거로 되돌리도록 하세요.

tabindex를 올바르게 다루라

tabindex에는 의미 있는 값이 세 가지 있으며, 각각의 용도가 다릅니다. tabindex='0'은 비인터랙티브 요소를 자연스러운 Tab 순서에 추가합니다 — 실제로 포커스를 받을 수 없는 요소(예: 커스텀 위젯 컨테이너)가 포커스를 받아야 할 때 사용하세요. tabindex='-1'은 요소를 Tab 순서에서 제거하지만, .focus()를 통해 프로그래밍 방식으로 포커스를 받을 수 있게 합니다 — 모달 타깃이나 skip 링크 목적지에 필수적입니다. 양수 tabindex 값(tabindex='1'이나 tabindex='5' 등)은 자연스러운 순서를 덮어쓰며, 대부분의 실제 사례에서 문제를 해결하기보다 더 많이 만듭니다. 이런 값은 완전히 피하고, 대신 DOM 순서를 수정하세요.

ARIA: 강력한 도구지만 만능 해결책은 아니다

Accessible Rich Internet Applications(ARIA) 속성은 HTML의 시맨틱을 확장해, 네이티브 HTML이 다루지 못하는 커스텀 컴포넌트 — 탭, 아코디언, 트리 뷰, 캐러셀, 콤보 박스 — 를 보조 기술이 이해할 수 있도록 돕습니다. ARIA 속성은 추가적인 문맥을 제공할 수 있지만, 시맨틱 HTML을 대체하는 것이 아니라 보완해야 합니다. 흔하고 위험한 실수는, 네이티브 HTML 요소로도 충분히 해결할 수 있는 상황에서 ARIA부터 찾는 것입니다.

ARIA의 첫 번째 규칙은 이렇습니다. 네이티브 HTML 요소나 속성으로 같은 일을 할 수 있다면 ARIA를 사용하지 말 것. 두 번째 규칙은, 잘못된 ARIA를 쓰느니 차라리 ARIA를 쓰지 않는 편이 낫다는 것입니다. 예를 들어, 적절한 role='menuitem' 자식 계층 없이 role='menu'를 적용하거나, 포커스를 받는 요소에 aria-hidden='true'를 사용하는 등 잘못된 ARIA 마크업은 접근성을 돕기는커녕 적극적으로 해칠 수 있습니다.

ARIA가 정말로 필요한 경우, 키보드 상호작용에 가장 유용한 속성은 다음과 같습니다. 접을 수 있는 요소의 열림/닫힘 상태를 전달하는 aria-expanded, 트리거와 그것이 제어하는 콘텐츠를 연결하는 aria-controls, 버튼이 메뉴나 다이얼로그를 연다는 것을 알리는 aria-haspopup, 배경 콘텐츠가 비활성 상태임을 알리기 위해 다이얼로그 요소에 사용하는 aria-modal='true', 그리고 포커스를 이동시키지 않고도 스크린 리더 사용자에게 동적 콘텐츠 변경을 알리는 aria-live 영역(polite는 상태 메시지, assertive는 긴급 알림용)입니다.

미묘하지만 중요한 한 가지 고려사항이 있습니다. NVDA와 JAWS 같은 스크린 리더는 자체 키보드 단축키를 사용합니다 — 예를 들어, NVDA에서 H 키를 누르면 페이지의 다음 헤딩으로 이동합니다. 개발자는 이러한 보조 기술 명령과 충돌하는 커스텀 애플리케이션 단축키를 만들지 않도록 주의해야 합니다.

키보드 접근성 테스트

지금 당장 실행할 수 있는 가장 효과적인 테스트는 도구가 필요 없습니다. 마우스를 뽑고 키보드만으로 웹사이트를 탐색해 보세요. Tab으로 인터랙티브 요소를 앞으로 이동하고, Shift+Tab으로 뒤로 이동하며, Enter로 링크와 버튼을 활성화하고, Space로 체크박스를 토글하고 버튼을 활성화하며, Escape로 모달과 메뉴를 닫고, 방향키로 컴포넌트 내부를 탐색합니다. 스스로에게 물어보세요. 모든 인터랙티브 요소에 도달할 수 있는가? 항상 내가 어디에 있는지 볼 수 있는가? 막히지 않고 모든 핵심 사용자 여정을 완료할 수 있는가?

자동화 도구는 키보드 접근성 문제의 의미 있는 일부 — 특히 누락된 레이블, 비어 있는 버튼, 일부 포커스 관리 문제 — 를 잡아낼 수 있습니다. axe DevTools, WAVE, Lighthouse 같은 도구는 유용한 1차 점검 도구입니다. 하지만 자동화 도구는 WCAG 문제의 약 40%만 감지합니다. 포커스 가시성, 논리적인 포커스 순서, 올바른 ARIA 상태 관리는 모두 사람의 수동 평가가 필요합니다. 가장 철저한 평가를 위해서는, 자동 스캔과 수동 키보드 전용 테스트를 여러 브라우저에서 병행하고, NVDA(Windows), JAWS(Windows), VoiceOver(macOS/iOS)로 스크린 리더 테스트를 포함해야 합니다.

새 컴포넌트나 페이지를 배포할 때마다 반드시 수동으로 테스트해야 하는 구체적인 시나리오는 다음과 같습니다. Tab, Enter, Escape만으로 모든 드롭다운, 모달, 아코디언을 열고 닫을 수 있는가? 모달이 닫힐 때 포커스가 트리거로 돌아오는가? 첫 번째 Tab 입력에서 skip navigation 링크가 나타나고 제대로 동작하는가? 포커스 표시기가 보이지 않는 요소로 Tab 포커스가 사라지는 지점은 없는가? sticky 헤더나 쿠키 배너가 페이지를 Tab으로 이동할 때 포커스를 받은 요소를 가리고 있지는 않은가?

컴포넌트 라이브러리를 구축하는 팀에게, W3C가 발행한 WAI-ARIA Authoring Practices Guide(APG)는 아코디언, 캐러셀부터 날짜 선택기, 트리 뷰에 이르기까지 수십 가지 위젯 유형에 대한 키보드 상호작용 패턴의 결정적 참고 자료입니다. 각 패턴은 어떤 키를 지원해야 하는지, 그리고 기대되는 동작이 무엇인지 정확히 명시합니다.

Sticky 헤더, 고정 푸터, 그리고 포커스 가림

WCAG 2.2에서 실무적으로 가장 관련성이 높은 새로운 요구사항 중 하나는 성공 기준 2.4.11: Focus Not Obscured입니다. 이는 현대 웹을 정의한다고 해도 과언이 아닐 정도로 흔한 문제를 다룹니다. sticky 내비게이션 바, 쿠키 동의 배너, 채팅 위젯, 고정 푸터가 페이지 콘텐츠 위에 겹쳐 있는 상황입니다. 키보드 사용자가 이런 고정 레이어 뒤로 스크롤된 요소로 Tab을 이동하면, 포커스를 받은 요소가 보이지 않게 됩니다 — 사용자는 자신이 무엇과 상호작용하는지 볼 수 없습니다.

해결책은 CSS와 JavaScript의 협업을 필요로 합니다. 요소가 포커스를 받을 때, 브라우저는 그 요소를 보이는 영역으로 스크롤해야 합니다. 하지만 높이가 예를 들어 80px인 position: fixed sticky 헤더가 있으면, 뷰포트 상단 80px은 항상 차지된 상태입니다. CSS scroll padding으로 이를 보정할 수 있습니다.

html {
  /* sticky 헤더 높이 + 약간의 여유 */
  scroll-padding-top: 96px;
}

이 설정은 브라우저에 스크롤 기준점을 지정된 만큼 오프셋하라고 알려, 포커스를 받은 요소를 자동으로 뷰에 스크롤할 때 고정 헤더를 고려하도록 합니다. sticky 푸터가 있다면 scroll-padding-bottom도 필요할 수 있습니다. 나타났다 사라지는 쿠키 배너나 오버레이의 경우, 포커스를 받은 요소를 가리지 않도록 적절한 z-index 값을 부여하거나, 배너가 보이는 동안 동적으로 scroll padding을 조정하세요.

핵심 요약

  • 시맨틱 HTML이 최고의 첫걸음이다. <button>, <a>, 폼 컨트롤 같은 네이티브 요소는 기본적으로 키보드 접근성이 있습니다. div로 만든 모든 커스텀 위젯은 접근성을 잃게 만들고, 이를 다시 ARIA와 JavaScript로 재구현해야 합니다.
  • 포커스 표시기를 대체 없이 제거하지 말라. 전역 outline: none 규칙은 스타일시트에 넣을 수 있는 것 중 가장 해로운 것 중 하나입니다. :focus-visible을 사용해, WCAG 2.2의 최소 크기와 대비 요구사항을 충족하는 스타일링된 고대비 포커스 링을 제공하세요.
  • 모든 동적 상호작용에 대해 포커스를 프로그래밍 방식으로 관리하라. 모달, 드로어, 토스트 알림, 동적 콘텐츠 변경은 모두 명시적인 포커스 관리 — 포커스를 안으로 이동시키고, 닫힐 때 되돌리는 작업 — 를 필요로 합니다. 이것이 없으면, UI가 바뀔 때마다 키보드 사용자는 자신의 위치를 잃습니다.
  • 모든 페이지 상단에 skip navigation 링크를 추가하라. 20줄도 안 되는 코드로 구현할 수 있으며, 그렇지 않으면 매 페이지 로드마다 헤더와 내비게이션 전체를 Tab으로 통과해야 하는 키보드 사용자 경험을 극적으로 개선합니다.
  • 배포 전에 키보드로 테스트하라. 자동화 도구는 키보드 접근성 문제의 일부만 잡아냅니다. 가장 중요한 사용자 여정을 키보드만으로 10분 정도 점검해 보면, 어떤 스캐너도 찾지 못할 실제 차단 요소를 발견할 수 있으며 — 팀의 어느 개발자든 특별한 교육 없이 수행할 수 있습니다.