Skip to content

키보드 네비게이션

19.3.1. 포커스 관리

모든 인터랙티브 UI 요소는 키보드로 접근하고 조작할 수 있어야 합니다. tabindex 속성은 다음 규칙에 따라 사용합니다.

tabindex 값의미사용 규칙
0자연스러운 Tab 순서에 포함비인터랙티브 요소를 포커스 가능하게 만들 때 사용
-1Tab 순서에서 제외, 프로그래밍 방식으로 포커스 가능모달, 동적 콘텐츠 등에서 스크립트로 포커스를 이동할 때 사용
양수 (1 이상)사용 금지Tab 순서를 임의로 변경하여 예측 불가능한 동작을 유발함
  • <button>, <a>, <input> 등 네이티브 인터랙티브 요소는 기본적으로 포커스를 받으므로 tabindex를 별도로 지정하지 않습니다.
  • <div>, <span> 등 비인터랙티브 요소에 클릭 이벤트를 바인딩하는 것을 지양합니다. 대신 <button> 요소를 사용합니다.

포커스 표시기(Focus Ring)는 모든 포커스 가능 요소에 시각적으로 표시해야 합니다.

css
/* 포커스 표시기 기본 스타일 */
:focus-visible {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
}

/* outline을 제거하는 것은 금지 */
/* 잘못된 예: *:focus { outline: none; } */
  • outline: none으로 포커스 표시기를 제거하지 않습니다. 커스텀 스타일로 대체하는 경우에만 outline: none을 사용하되, 반드시 동등한 시각적 표시를 제공해야 합니다.
  • :focus-visible을 사용하여 키보드 포커스에만 표시기를 적용하는 것을 권장합니다.

19.3.2. Tab 순서

Tab 키로 이동하는 순서는 콘텐츠의 논리적 흐름과 시각적 배치 순서를 따라야 합니다.

  • DOM 순서가 시각적 순서와 일치해야 합니다. CSS의 order, flex-direction: row-reverse, position: absolute 등으로 시각적 순서만 변경하면 Tab 순서와 불일치가 발생합니다.
  • 레이아웃을 CSS로 재배치해야 하는 경우, DOM 순서 자체를 변경하여 논리적 흐름을 유지합니다.
  • 동적으로 삽입된 콘텐츠(토스트, 드롭다운 등)가 Tab 순서를 방해하지 않도록 배치합니다.
vue
<!-- 올바른 예: DOM 순서 = 시각적 순서 -->
<template>
  <nav>
    <a href="/home">홈</a>
    <a href="/about">소개</a>
    <a href="/contact">문의</a>
  </nav>
  <main>
    <h1>페이지 제목</h1>
    <p>본문 내용</p>
  </main>
</template>

<!-- 잘못된 예: CSS로 시각적 순서만 역전 -->
<!--
<template>
  <div class="flex flex-row-reverse">
    <button>첫 번째로 보이지만 Tab은 마지막</button>
    <button>마지막으로 보이지만 Tab은 첫 번째</button>
  </div>
</template>
-->

19.3.3. 키보드 단축키

인터랙티브 컴포넌트는 다음 키보드 동작을 지원해야 합니다.

동작적용 대상
Enter버튼 클릭, 폼 제출, 링크 활성화버튼, 폼, 링크
Escape모달 닫기, 드롭다운 닫기, 작업 취소모달, 드롭다운, 팝오버
Space체크박스 토글, 버튼 클릭체크박스, 버튼
방향키탭 패널 전환, 메뉴 항목 이동, 라디오 버튼 선택탭, 메뉴, 라디오 그룹
vue
<script setup lang="ts">
import { ref } from 'vue'

const isOpen = ref(false)

function handleKeydown(event: KeyboardEvent) {
  if (event.key === 'Escape') {
    isOpen.value = false
  }
}
</script>

<template>
  <div @keydown="handleKeydown">
    <button @click="isOpen = !isOpen">
      메뉴 열기
    </button>
    <ul v-if="isOpen" role="menu">
      <li role="menuitem" tabindex="0">항목 1</li>
      <li role="menuitem" tabindex="0">항목 2</li>
      <li role="menuitem" tabindex="0">항목 3</li>
    </ul>
  </div>
</template>
  • @keydown 이벤트로 키보드 동작을 처리합니다. @keypress는 사용하지 않습니다.
  • Vue의 키 수식어(@keydown.escape, @keydown.enter)를 활용하는 것을 권장합니다.
  • 커스텀 키보드 단축키를 제공하는 경우, 브라우저 및 운영 체제의 기본 단축키와 충돌하지 않아야 합니다.

19.3.4. 스킵 네비게이션

반복되는 내비게이션 영역을 건너뛰고 본문 콘텐츠로 직접 이동할 수 있는 스킵 네비게이션 링크를 제공해야 합니다.

vue
<template>
  <a
    href="#main-content"
    class="skip-nav"
  >
    본문 바로가기
  </a>
  <header>
    <nav>
      <!-- 내비게이션 항목 -->
    </nav>
  </header>
  <main id="main-content" tabindex="-1">
    <!-- 본문 콘텐츠 -->
  </main>
</template>

<style scoped>
.skip-nav {
  position: absolute;
  top: -100%;
  left: 0;
  z-index: 9999;
  padding: 8px 16px;
  background-color: #ffffff;
  color: #1a1a1a;
  font-weight: bold;
  text-decoration: none;
}

.skip-nav:focus {
  top: 0;
}
</style>
  • 스킵 네비게이션 링크는 페이지의 최상단 첫 번째 요소로 배치해야 합니다.
  • 평상시에는 화면에 보이지 않으며, 포커스를 받았을 때만 화면에 표시됩니다.
  • 링크 대상(#main-content)에 tabindex="-1"을 지정하여 포커스를 수신할 수 있도록 합니다.
  • 내비게이션 영역이 복잡한 경우, 검색 영역이나 사이드바로의 바로가기 링크를 추가로 제공할 수 있습니다.

TIENIPIA QUALIFIED STANDARD