キーボードナビゲーション
すべてのユーザーがマウスなしで効率的にナビゲートできるようにする
キーボードショートカット
- Tab
- インタラクティブ要素間を移動
- Shift + Tab
- 後方ナビゲーション
- Entrée / Espace
- ボタンとリンクを有効化
- Échap
- モーダルとメニューを閉じる
- ↑ ↓
- リストとメニューで移動
リストでのキーボードナビゲーション
❌ 悪い例
Tabナビゲーションのみのメニュー:
🏠 ホーム
📋 プロジェクト
👥 チーム
📊 統計
⚙️ 設定
<!-- 悪い例:Tabナビゲーションのみ -->
<div class="menu">
<div tabindex="0">🏠 ホーム</div>
<div tabindex="0">📋 プロジェクト</div>
<div tabindex="0">👥 チーム</div>
<div tabindex="0">📊 統計</div>
<div tabindex="0">⚙️ 設定</div>
</div>
<!-- ユーザーは5回Tabを押す必要がある -->
<!-- 矢印ナビゲーションなし -->
<!-- メニューのセマンティクスなし -->✅ 良い例
矢印ナビゲーションを使用したメニュー:
🏠 ホーム
📋 プロジェクト
👥 チーム
📊 統計
⚙️ 設定
💡 ↑ ↓を使用して移動、Enterで選択
<!-- 良い例:roving tabindexを使用した矢印ナビゲーション -->
<div
role="menu"
@keydown="handleKeydown"
>
<div
v-for="(item, i) in items"
role="menuitem"
:tabindex="selected === i ? 0 : -1"
@focus="selected = i"
:class="{ selected: selected === i }"
>
{{ item }}
</div>
</div>// roving tabindexを使用した矢印ナビゲーション
function handleKeydown(e) {
switch(e.key) {
case 'ArrowDown':
selected = (selected + 1) % items.length
break
case 'ArrowUp':
selected = selected === 0
? items.length - 1
: selected - 1
break
case 'Enter':
selectItem(selected)
break
}
}説明: キーボードでナビゲート可能なリストは、従来のTabナビゲーションに加えて、矢印キーを使用して要素間を移動できます。
可視フォーカススタイル
❌ 悪い例
この領域でTabを使用してナビゲートしてみてください:
/* ❌ すべてのフォーカスインジケーターを削除 */
*:focus {
outline: none !important;
}
button:focus,
a:focus,
input:focus {
outline: none !important;
}<!-- ❌ アクセシビリティの問題: -->
<button>ボタン1</button> <!-- タイプなし、フォーカスが見えない -->
<button>ボタン2</button> <!-- タイプなし、フォーカスが見えない -->
<a href="#">リンク</a> <!-- フォーカスが見えない -->
<input type="text" /> <!-- フォーカスが見えない -->
<!-- キーボードナビゲーションが視覚的に追跡不可能 -->✅ 良い例
この領域でTabを使用してナビゲートしてみてください:
/* ✅ 可視でアクセシブルなフォーカスインジケーター */
button:focus-visible {
outline: 3px solid #4F46E5;
outline-offset: 2px;
}
input:focus-visible {
border-color: #4F46E5;
outline: 2px solid #4F46E5;
outline-offset: 1px;
}<button type="button">ボタン1</button>
<button type="button">ボタン2</button>
<a href="#">ページへのリンク</a>
<input type="text" placeholder="テキストを入力" />説明: キーボードでナビゲートするユーザーは、ページ上のどこにいるかを知るために明確な視覚的指標が必要です。
スキップリンク(コンテンツへスキップ)
❌ 悪い例
✅ 良い例
/* デフォルトで非表示のスキップリンク、フォーカス時に可視 */
.skip-link {
position: absolute;
top: -100px;
left: 1rem;
z-index: 1000;
padding: 0.5rem 1rem;
background: var(--color-primary);
color: white;
text-decoration: none;
border-radius: 0.625rem;
}
.skip-link:focus {
top: 1rem;
}<a href="#main-content" class="skip-link">
メインコンテンツへスキップ
</a>
<header>
<nav aria-label="メインナビゲーション">
<ul>
<li><a href="/">ホーム</a></li>
<li><a href="/produits">製品</a></li>
<li><a href="/services">サービス</a></li>
</ul>
</nav>
</header>
<main id="main-content">
<h1>メインコンテンツ</h1>
</main>説明: スキップリンクを使用すると、キーボードユーザーはすべてのナビゲーションを通過することなく、メインコンテンツに直接ジャンプできます。
フォーカストラップ付きモーダル
❌ 悪い例
<!-- 悪い例:フォーカス管理なしのモーダル -->
<div v-if="modalOpen" class="modal-overlay" @click="close">
<div class="modal-content" @click.stop>
<div class="modal-header">
<h3>アクションを確認</h3>
<button @click="close">×</button>
</div>
<div class="modal-body">
<p>続行してもよろしいですか?</p>
<input type="text" placeholder="理由(オプション)" />
</div>
<div class="modal-footer">
<button @click="close">キャンセル</button>
<button @click="confirm">確認</button>
</div>
</div>
</div>✅ 良い例
<!-- 良い例:フォーカストラップ付きモーダル -->
<div
v-if="modalOpen"
class="modal-overlay"
@click="closeModal"
@keydown.esc="closeModal"
>
<div
class="modal-content"
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
@click.stop
>
<header class="modal-header">
<h2 id="modal-title">アクションを確認</h2>
<button
type="button"
@click="closeModal"
aria-label="モーダルを閉じる"
class="close-btn"
>×</button>
</header>
<div class="modal-body">
<p>続行してもよろしいですか?</p>
<input
ref="firstInput"
type="text"
placeholder="理由(オプション)"
@keydown.tab="handleFocusTrap"
/>
</div>
<footer class="modal-footer">
<button
type="button"
@click="closeModal"
@keydown.tab="handleFocusTrap"
>キャンセル</button>
<button
type="button"
ref="lastButton"
@click="confirmAction"
@keydown.tab="handleFocusTrap"
>確認</button>
</footer>
</div>
</div>// 必須機能 :
// - 最初の要素への自動フォーカス
// - フォーカストラップ(Tab/Shift+Tab)
// - Escapeで閉じる
// - トリガー要素にフォーカスを戻す
function handleFocusTrap(event) {
const focusableElements = modalContent.querySelectorAll(
'button, input, [tabindex]:not([tabindex="-1"])'
)
const firstElement = focusableElements[0]
const lastElement = focusableElements[focusableElements.length - 1]
if (event.shiftKey && document.activeElement === firstElement) {
event.preventDefault()
lastElement.focus()
} else if (!event.shiftKey && document.activeElement === lastElement) {
event.preventDefault()
firstElement.focus()
}
}説明: モーダルはフォーカスを内部に保持し、Escapeキーで閉じることができる必要があります。フォーカスは、モーダルを開いた要素に戻る必要があります。