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 の orderflex-direction: row-reverseposition: 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