キーボードナビゲーション
19.3.1. フォーカス管理
すべてのインタラクティブ UI 要素はキーボードでアクセスし操作できなければなりません。tabindex 属性は以下の規則に従って使用します。
| tabindex 値 | 意味 | 使用規則 |
|---|---|---|
0 | 自然な Tab 順序に含まれる | 非インタラクティブ要素をフォーカス可能にする際に使用 |
-1 | Tab 順序から除外、プログラム的にフォーカス可能 | モーダル、動的コンテンツなどでスクリプトによりフォーカスを移動する際に使用 |
正の値(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"を指定してフォーカスを受信できるようにします。 - ナビゲーション領域が複雑な場合は、検索領域やサイドバーへのショートカットリンクを追加で提供することができます。