アクセシビリティ(a11y)
WCAG 2.2 と Lighthouse アクセシビリティ監査に基づく包括的なアクセシビリティガイドライン。目的: 障害を持つ人々を含むすべての人がコンテンツを利用できるようにすること。
エビデンスに基づく監査ワークフロー
レンダリングされたページが利用可能な場合:
- その機能が利用可能な場合は、ライブの Lighthouse アクセシビリティ監査を実行します。Chrome DevTools MCP では
lighthouse_auditを使用します。一般公開ページにはモバイルナビゲーションモードを、リロードすると認証状態やユーザー作成状態が失われる場合はスナップショットモードを使用します。 - 監査で失敗したノードを使用して、リポジトリ全体で汎用的なパターンを検索するのではなく、関連するコンポーネントまたはテンプレートを特定します。
- レンダリングされたアクセシビリティツリーのスナップショットを取得して、名前、役割、状態、ランドマーク、見出し構造を検査します。Chrome DevTools MCP では
take_snapshotを使用します。キーボードで影響を受けるフローを操作します。 - ソースを修正し、同じ監査と手動操作を再実行します。
ライブツールが利用できない場合は、Lighthouse CLI または axe を使用して自動カバレッジを取得し、同じ手動チェックを完了します。自動ツールはアクセシビリティバリアの一部のみを検出します。スコア 100 は WCAG 適合ではなく、低いスコアが問題レベルのエビデンスに取って代わるものではありません。
WCAG の原則: POUR
| 原則 | 説明 |
|---|---|
| Perceivable(知覚可能) | コンテンツをさまざまな感覚で知覚できる |
| Operable(操作可能) | インターフェースをすべてのユーザーが操作できる |
| Understandable(理解可能) | コンテンツとインターフェースが理解可能 |
| Robust(堅牢) | コンテンツが支援技術で動作する |
適合レベル
| レベル | 要件 | 目標 |
|---|---|---|
| A | 最小限のアクセシビリティ | 合格必須 |
| AA | 標準準拠 | 合格推奨(多くの法域で法的要件) |
| AAA | 強化されたアクセシビリティ | あると良い |
知覚可能
テキスト代替(1.1)
画像には alt テキストが必要:
<!-- ❌ alt なし -->
<img src="chart.png">
<!-- ✅ 説明的な alt -->
<img src="chart.png" alt="第3四半期の売上が40%増加したことを示す棒グラフ">
<!-- ✅ 装飾的な画像(空の alt) -->
<img src="decorative-border.png" alt="" role="presentation">
<!-- ✅ 複雑な画像に詳細な説明 -->
<figure>
<img src="infographic.png" alt="2024年の市場トレンドのインフォグラフィック"
aria-describedby="infographic-desc">
<figcaption id="infographic-desc">
<!-- 詳細な説明 -->
</figcaption>
</figure>
アイコンボタンにはアクセシブルな名前が必要:
<!-- ❌ アクセシブルな名前なし -->
<button><svg><!-- メニューアイコン --></svg></button>
<!-- ✅ aria-label を使用 -->
<button aria-label="メニューを開く">
<svg aria-hidden="true"><!-- メニューアイコン --></svg>
</button>
<!-- ✅ 視覚的に隠されたテキストを使用 -->
<button>
<svg aria-hidden="true"><!-- メニューアイコン --></svg>
<span class="visually-hidden">メニューを開く</span>
</button>
視覚的に隠されたクラス:
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
色のコントラスト(1.4.3、1.4.6)
| テキストサイズ | AA 最小 | AAA 強化 |
|---|---|---|
| 通常テキスト(< 18px / < 14px bold) | 4.5:1 | 7:1 |
| 大きなテキスト(≥ 18px / ≥ 14px bold) | 3:1 | 4.5:1 |
| UI コンポーネントとグラフィック | 3:1 | 3:1 |
/* ❌ 低コントラスト(2.5:1) */
.low-contrast {
color: #999;
background: #fff;
}
/* ✅ 十分なコントラスト(7:1) */
.high-contrast {
color: #333;
background: #fff;
}
/* ✅ フォーカス状態にもコントラストが必要(背景に対して 3:1、WCAG 1.4.11) */
:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
色だけで伝えない:
<!-- ❌ エラーを示すのは色のみ -->
<input class="error-border">
<style>.error-border { border-color: red; }</style>
<!-- ✅ 色 + アイコン + テキスト -->
<div class="field-error">
<input aria-invalid="true" aria-describedby="email-error">
<span id="email-error" class="error-message">
<svg aria-hidden="true"><!-- エラーアイコン --></svg>
有効なメールアドレスを入力してください
</span>
</div>
メディア代替(1.2)
<!-- キャプション付き動画 -->
<video controls>
<source src="video.mp4" type="video/mp4">
<track kind="captions" src="captions.vtt" srclang="en" label="English" default>
<track kind="descriptions" src="descriptions.vtt" srclang="en" label="Descriptions">
</video>
<!-- 文字起こし付き音声 -->
<audio controls>
<source src="podcast.mp3" type="audio/mp3">
</audio>
<details>
<summary>文字起こし</summary>
<p>全文の文字起こしテキスト...</p>
</details>
操作可能
キーボードアクセシブル(2.1)
すべての機能はキーボードからアクセス可能でなければなりません。 ネイティブのインタラクティブ要素(<button>、<a href>、フォームコントロール)を優先してください。これらは Enter/Space キーによるアクティベーション、フォーカス、支援技術のセマンティクスを自動で処理します。ネイティブ要素を使用できない場合にのみ、手動でキーボードハンドリングを追加してください。
<!-- ❌ クリックのみの非インタラクティブ要素: フォーカス不可、キーボードアクティベーションなし -->
<div class="card" onclick="handleAction()">開く</div>
<!-- ✅ ベスト: ネイティブの button を使用 -->
<button type="button" onclick="handleAction()">開く</button>
// ✅ 非インタラクティブ要素(例: role="button" の div)をどうしても使用する場合、
// フォーカス可能にし、キーボードアクティベーションも処理します。ネイティブの
// <button> にはこれを追加しないでください。Enter/Space は既に click を発火するため、
// 二重トリガーになります。
element.setAttribute('role', 'button');
element.setAttribute('tabindex', '0');
element.addEventListener('click', handleAction);
element.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleAction();
}
});
キーボードトラップは禁止。 ユーザーはすべてのコンポーネントに Tab キーで出入りできなければなりません。ダイアログにはモーダルフォーカストラップパターンを使用してください。ネイティブの <dialog> 要素はこれを自動的に処理します。
フォーカス表示(2.4.7)
/* ❌ フォーカスアウトラインを削除しない */
*:focus { outline: none; }
/* ✅ キーボードのみのフォーカスには :focus-visible を使用 */
:focus {
outline: none;
}
:focus-visible {
outline: 2px solid currentColor; /* テキスト色を継承 → コントラストチェック済み */
outline-offset: 2px;
}
/* ✅ またはブランドカラーを選択し、それが表示されるすべての背景に対して ≥3:1 のコントラストを確認 */
button:focus-visible {
box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.5);
}
フォーカスが隠されていない(2.4.11)— 2.2 で新規
要素がキーボードフォーカスを受け取ったとき、固定ヘッダー、フッター、重なり合うパネルなど、作成者が作成した他のコンテンツによって完全に隠されてはなりません。レベル AAA(2.4.12)では、フォーカスされた要素のどの部分も隠されてはいけません。
/* ✅ フォーカスされた要素にスクロールする際に固定ヘッダーを考慮 */
:target {
scroll-margin-top: 80px;
}
/* ✅ フォーカスされたアイテムが固定/スティックバーをクリアすることを確認 */
:focus {
scroll-margin-top: 80px;
scroll-margin-bottom: 60px;
}
スキップリンク(2.4.1)
キーボードユーザーが繰り返しのナビゲーションをスキップできるように、スキップリンクを提供します。完全なマークアップとスタイルについては、スキップリンクパターンを参照してください。
ターゲットサイズ(2.5.8)— 2.2 で新規
インタラクティブターゲットは、少なくとも 24 × 24 CSS ピクセル(AA)である必要があります。例外: インラインテキストリンク、ブラウザがサイズを制御する要素、およびバウンディングボックスを中心とした 24px の円が別のターゲットと重ならないターゲット。
/* ✅ 最小ターゲットサイズ */
button,
[role="button"],
input[type="checkbox"] + label,
input[type="radio"] + label {
min-width: 24px;
min-height: 24px;
}
/* ✅ 快適なターゲットサイズ(推奨 44×44) */
.touch-target {
min-width: 44px;
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
}
ドラッグ動作(2.5.7)— 2.2 で新規
ドラッグを必要とするアクションには、単一ポインターの代替手段(例: ボタン、入力)が必要です。並べ替え可能リストの例については、ドラッグ動作パターンを参照してください。
タイミング(2.2)
// ユーザーが制限時間を延長できるようにする
function showSessionWarning() {
const modal = createModal({
title: 'セッションの期限切れ',
content: 'セッションは 2 分で期限切れになります。',
actions: [
{ label: 'セッションを延長', action: extendSession },
{ label: 'ログアウト', action: logout }
],
timeout: 120000
});
}
モーション(2.3)
/* 縮小モーション設定を尊重 */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
理解可能
ページ言語(3.1.1)
<!-- ❌ 言語が指定されていない -->
<html>
<!-- ✅ 言語が指定されている -->
<html lang="en">
<!-- ✅ ページ内で言語が変更される -->
<p>フランス語でこんにちはは <span lang="fr">bonjour</span> です。</p>
一貫したナビゲーション(3.2.3)
<!-- ナビゲーションはページ間で一貫している必要があります -->
<nav aria-label="メイン">
<ul>
<li><a href="/" aria-current="page">ホーム</a></li>
<li><a href="/products">製品</a></li>
<li><a href="/about">概要</a></li>
</ul>
</nav>
一貫したヘルプ(3.2.6)— 2.2 で新規
ヘルプメカニズム(連絡先情報、チャットウィジェット、FAQ リンク、セルフヘルプオプション)が複数のページで繰り返される場合、毎回 同じ相対的な順序 で表示される必要があります。一貫した配置に依存するユーザーが、ページごとにヘルプを探す必要があってはいけません。
フォームラベル(3.3.2)
すべての入力には、プログラム的に関連付けられたラベルが必要です。明示的、暗黙的、および指示的な例については、フォームラベルパターンを参照してください。
エラーハンドリング(3.3.1、3.3.3)
エラーをスクリーンリーダーに通知するには、role="alert" または aria-live を使用し、無効なフィールドには aria-invalid="true" を設定し、送信時に最初のエラーにフォーカスします。完全なマークアップと JS については、エラーハンドリングパターンを参照してください。
冗長な入力(3.3.7)— 2.2 で新規
同じセッション内でユーザーが既に提供した情報を再入力させることは避けてください。以前のステップから自動入力するか、以前に入力した値から選択できるようにします。例外: セキュリティの再確認と期限切れのコンテンツ。
<!-- ✅ 請求先住所から配送先住所を自動入力 -->
<fieldset>
<legend>配送先住所</legend>
<label>
<input type="checkbox" id="same-as-billing" checked>
請求先住所と同じ
</label>
<!-- チェックされている場合、フィールドは自動入力されます -->
</fieldset>
アクセシブルな認証(3.3.8)— 2.2 で新規
ログインフローは、認知機能テスト(例: パスワードの記憶、パズルの解決)に依存してはなりません。ただし、以下の少なくとも 1 つが当てはまる場合を除く:
- コピーペーストまたは自動入力メカニズムが利用可能
- 代替方法が存在する(例: パスキー、SSO、メールリンク)
- テストが物体認識または個人コンテンツを使用する(AA のみ。AAA はこの例外を削除)
<!-- ✅ パスワードフィールドで貼り付けを許可 -->
<input type="password" id="password" autocomplete="current-password">
<!-- ✅ パスワードレスな代替手段を提供 -->
<button type="button">パスキーでサインイン</button>
<button type="button">ログインリンクをメールで送信</button>
堅牢
ARIA の使用(4.1.2)
ネイティブ要素を優先:
<!-- ❌ div に ARIA role -->
<div role="button" tabindex="0">クリック</div>
<!-- ✅ ネイティブの button -->
<button>クリック</button>
<!-- ❌ ARIA チェックボックス -->
<div role="checkbox" aria-checked="false">オプション</div>
<!-- ✅ ネイティブのチェックボックス -->
<label><input type="checkbox"> オプション</label>
ARIA が必要な場合 は、正しいロールと状態を使用してください。完全なタブリストの例については、ARIA タブパターンを参照してください。
ライブリージョン(4.1.3)
aria-live リージョンを使用して、フォーカスを移動せずに動的なコンテンツ変更を通知します。マークアップと showNotification() ヘルパーについては、ライブリージョンパターンを参照してください。
テストチェックリスト
自動テスト
エージェントに直接失敗したレンダリングノードを返すライブの Lighthouse 監査を優先します。Chrome DevTools MCP では、これは lighthouse_audit です。それ以外の場合は:
# Lighthouse アクセシビリティ監査
npx lighthouse https://example.com --only-categories=accessibility
# axe-core
npm install @axe-core/cli -g
axe https://example.com
手動テスト
- キーボードナビゲーション: ページ全体を Tab キーで移動し、Enter/Space でアクティベーション
- スクリーンリーダー: VoiceOver(Mac)、NVDA(Windows)、または TalkBack(Android)でテスト
- ズーム: 200% ズームでコンテンツが利用可能
- ハイコントラスト: Windows ハイコントラストモードでテスト
- 縮小モーション:
prefers-reduced-motion: reduceでテスト - フォーカス順序: 論理的で視覚的な順序に従っている
- ターゲットサイズ: インタラクティブ要素が 24×24px の最小サイズを満たしている
VoiceOver と NVDA のショートカットについては、スクリーンリーダーコマンドリファレンスを参照してください。
影響度別のよくある問題
重大(すぐに修正)
- フォームラベルの欠落
- 画像の alt テキストの欠落
- 不十分な色のコントラスト
- キーボードトラップ
- フォーカスインジケーターなし
深刻(リリース前に修正)
- ページ言語の欠落
- 見出し構造の欠落
- 説明的でないリンクテキスト
- 自動再生メディア
- スキップリンクの欠落
中程度(近いうちに修正)
- アイコンの ARIA ラベルの欠落
- 一貫性のないナビゲーション
- エラー識別の欠落
- コントロールのないタイミング
- ランドマークリージョンの欠落


