웹 접근성 검사·수정 도우미 WCAG 2.1 진단 · 수정 코드 · 작업 지시서
← 접근성 아티클 목록

키보드만으로 쓸 수 있는 사이트 만들기

포커스 관리 실전 · WCAG 2.1 성공기준 2.1.1 · 2.4.3 · 2.4.7 · 2026년 9월

마우스를 쓰지 못하는 사람은 생각보다 많습니다. 손 떨림·반복성 긴장 손상으로 정밀한 포인팅이 어려운 사용자, 화면낭독기 사용자(대부분 키보드로 조작), 보조 스위치 사용자, 그리고 그냥 키보드가 빠른 개발자까지. 키보드 접근성은 "모든 기능을 Tab·Enter·Space·방향키로 쓸 수 있는가" 한 문장으로 요약됩니다. 그리고 대부분의 문제는 "포커스"를 다루는 방식에서 나옵니다.

1. 기본기 — 시맨틱 요소를 쓰면 절반은 끝난다

<a>, <button>, <input>, <select>, <textarea>는 브라우저가 알아서 포커스를 받고, Enter·Space 동작을 제공합니다. 문제는 이걸 <div>로 흉내 낼 때 시작됩니다.

<!-- 키보드로 도달 불가, Enter로 실행 불가 -->
<div class="btn" onclick="submit()">보내기</div>

<!-- 이렇게 -->
<button type="button" onclick="submit()">보내기</button>

div여야 한다면 tabindex="0", role="button", Enter/Space 키 핸들러를 직접 다 붙여야 합니다. 이 세 가지를 손으로 관리하느니 <button>을 쓰고 CSS로 모양을 잡는 편이 훨씬 안전합니다.

2. 포커스 표시를 지우지 마세요

가장 흔하고, 가장 치명적인 실수입니다. 아래 한 줄이 전역 CSS에 있으면 키보드 사용자는 자기가 지금 화면 어디에 있는지 전혀 알 수 없습니다.

/* 이러지 마세요 */
*:focus { outline: none; }

기본 아웃라인의 모양이 마음에 안 든다면 지우지 말고 바꾸세요. :focus-visible를 쓰면 마우스 클릭 때는 표시가 안 뜨고 키보드 이동 때만 뜹니다.

:focus-visible {
  outline: 2px solid #1a73e8;
  outline-offset: 2px;
  border-radius: 3px;
}
/* 마우스 클릭 시의 잔상만 정리 (표시는 위에서 이미 담당) */
:focus:not(:focus-visible) { outline: none; }

표시는 대비 3:1 이상이어야 하고(WCAG 1.4.11), 요소를 최소 2px 두께로 감싸는 것이 좋습니다. 배경색만 살짝 바꾸는 방식은 색약 사용자에게 안 보일 수 있습니다.

3. 탭 순서는 화면 순서와 같아야 한다

Tab을 눌렀을 때 포커스가 이동하는 순서는 DOM 순서를 따릅니다. CSS order, flex-direction: row-reverse, position 등으로 보이는 순서만 바꾸면 탭 순서와 어긋나 사용자가 혼란스러워집니다(WCAG 2.4.3).

4. 스킵 링크 — 반복 영역 건너뛰기

페이지마다 상단에 헤더·GNB가 20~30개 링크로 깔려 있으면, 키보드 사용자는 본문에 닿기 위해 매번 그걸 다 지나야 합니다. 페이지 첫 요소로 "본문 바로가기" 링크를 두면 해결됩니다(WCAG 2.4.1).

<body>
  <a href="#main" class="skip-link">본문 바로가기</a>
  <header>...</header>
  <main id="main" tabindex="-1">...</main>

<style>
.skip-link {
  position: absolute; left: 8px; top: -50px;
  background: #fff; color: #1a1a1a; padding: 10px 14px; border-radius: 6px;
  transition: top .15s;
}
.skip-link:focus { top: 8px; }  /* 포커스 받을 때만 화면에 등장 */
</style>

display: none으로 숨기면 포커스를 못 받으니, 화면 밖으로 밀어 두었다가 :focus에서 끌어오는 방식을 씁니다. 이 페이지 맨 위에서 Tab을 눌러 직접 확인해 보세요.

5. 모달·팝업 — 포커스 3원칙

대화상자를 열 때 지켜야 할 것은 세 가지입니다.

  1. 열면 포커스를 대화상자 안으로 옮긴다(대개 첫 요소나 제목).
  2. 열려 있는 동안 Tab이 대화상자 밖으로 나가지 않는다(포커스 가두기).
  3. 닫으면 포커스를 열기 전에 눌렀던 그 버튼으로 되돌린다.
let lastFocused;

function openDialog(dialog) {
  lastFocused = document.activeElement;
  dialog.hidden = false;
  const focusables = dialog.querySelectorAll(
    'a[href], button:not([disabled]), input, select, textarea, [tabindex]:not([tabindex="-1"])'
  );
  const first = focusables[0], last = focusables[focusables.length - 1];
  first.focus();

  dialog.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') { closeDialog(dialog); return; }
    if (e.key !== 'Tab') return;
    if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
    else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
  });
}

function closeDialog(dialog) {
  dialog.hidden = true;
  lastFocused && lastFocused.focus();
}

마크업에는 role="dialog", aria-modal="true", 제목과 연결하는 aria-labelledby가 필요합니다. 네이티브 <dialog> 요소를 showModal()로 열면 포커스 가두기와 Esc 닫기를 브라우저가 처리해 주므로, 새로 만든다면 이쪽을 먼저 검토하세요.

6. 복합 위젯 — roving tabindex

탭 목록, 툴바, 메뉴, 라디오 그룹 같은 위젯은 위젯 전체가 Tab 한 번, 그 안의 이동은 방향키로 하는 것이 표준 패턴입니다(ARIA Authoring Practices). 자식 10개가 전부 탭 정지점이면 사용자가 피곤합니다.

<div role="tablist" aria-label="설정">
  <button role="tab" aria-selected="true"  tabindex="0">일반</button>
  <button role="tab" aria-selected="false" tabindex="-1">알림</button>
  <button role="tab" aria-selected="false" tabindex="-1">보안</button>
</div>

// 방향키로 이동하며 tabindex를 "굴린다"
tablist.addEventListener('keydown', (e) => {
  if (!['ArrowRight', 'ArrowLeft'].includes(e.key)) return;
  const tabs = [...tablist.querySelectorAll('[role=tab]')];
  const i = tabs.indexOf(document.activeElement);
  const next = e.key === 'ArrowRight'
    ? (i + 1) % tabs.length
    : (i - 1 + tabs.length) % tabs.length;
  tabs.forEach(t => t.tabIndex = -1);
  tabs[next].tabIndex = 0;
  tabs[next].focus();
});

7. 그 밖에 자주 걸리는 것

8. 5분 점검법

  1. 마우스에서 손을 떼고 Tab으로 페이지를 처음부터 끝까지 통과한다.
  2. 지금 포커스가 어디 있는지 항상 눈에 보이는가?
  3. 탭 순서가 읽는 순서와 같은가?
  4. 메뉴·모달·탭·슬라이더 등 모든 기능이 실행되고, 빠져나올 수 있는가?
  5. 모달을 닫았을 때 포커스가 열었던 버튼으로 돌아오는가?

이 도구의 자동 검사div onclick, 양수 tabindex, 스킵 링크 부재, outline:none 패턴을 잡아 줍니다. 실제 탭 순서와 포커스 흐름은 위 5분 점검으로 직접 확인하는 것이 가장 확실합니다.

관련 글
· 접근 가능한 입력 양식 만들기
· 화면낭독기로 내 사이트 직접 들어보기
· 웹 접근성 실무 가이드 — 전체 개요
문의: dkdnj123@gmail.com