アクセシビリティ向上完全ガイド
アクセシビリティ向上完全ガイド
Section titled “アクセシビリティ向上完全ガイド”Webアプリケーションのアクセシビリティを向上させる実践的な方法を、実務で使える実装例とベストプラクティスとともに詳しく解説します。
1. セマンティックHTML
Section titled “1. セマンティックHTML”セマンティックHTMLとは
Section titled “セマンティックHTMLとは”セマンティックHTMLは、HTML要素の意味を明確に表現するHTMLの書き方です。
問題のある実装:
<!-- ❌ 悪い例: divとspanのみで構成 --><div class="header"> <div class="title">サイトタイトル</div> <div class="nav"> <div class="nav-item">ホーム</div> <div class="nav-item">について</div> </div></div><div class="main"> <div class="article"> <div class="heading">記事タイトル</div> <div class="content">記事の内容</div> </div></div>改善された実装:
<!-- ✅ 良い例: セマンティックなHTML要素を使用 --><header> <h1>サイトタイトル</h1> <nav> <ul> <li><a href="/">ホーム</a></li> <li><a href="/about">について</a></li> </ul> </nav></header><main> <article> <h2>記事タイトル</h2> <p>記事の内容</p> </article></main>主要なセマンティック要素
Section titled “主要なセマンティック要素”<!-- 構造的な要素 --><header>ヘッダー</header><nav>ナビゲーション</nav><main>メインコンテンツ</main><article>独立した記事</article><section>セクション</section><aside>補足情報</aside><footer>フッター</footer>
<!-- 見出し要素 --><h1>最上位の見出し</h1><h2>2番目の見出し</h2><h3>3番目の見出し</h3>
<!-- リスト要素 --><ul> <li>項目1</li> <li>項目2</li></ul>
<ol> <li>順序付き項目1</li> <li>順序付き項目2</li></ol>2. キーボード操作
Section titled “2. キーボード操作”キーボード操作の実装
Section titled “キーボード操作の実装”すべてのインタラクティブ要素はキーボードで操作可能である必要があります。
問題のある実装:
<!-- ❌ 悪い例: キーボードで操作できない --><div onclick="handleClick()" class="button">クリック</div>改善された実装:
<!-- ✅ 良い例: キーボードで操作可能 --><button onclick="handleClick()">クリック</button>
<!-- または、divを使用する場合は適切な属性を追加 --><div role="button" tabindex="0" onclick="handleClick()" onkeydown="if(event.key === 'Enter' || event.key === ' ') handleClick()" class="button"> クリック</div>フォーカス管理
Section titled “フォーカス管理”<!-- フォーカス可能な要素 --><button>ボタン</button><a href="/">リンク</a><input type="text"><select> <option>選択肢1</option></select><textarea></textarea>
<!-- フォーカス順序の制御 --><div tabindex="0">フォーカス可能</div><div tabindex="-1">プログラム的にフォーカス可能</div><div tabindex="1">フォーカス順序を指定(非推奨)</div>フォーカスインジケーター
Section titled “フォーカスインジケーター”/* フォーカスインジケーターのスタイル */button:focus,a:focus,input:focus { outline: 2px solid #0066cc; outline-offset: 2px;}
/* カスタムフォーカススタイル */.custom-button:focus-visible { box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.5);}3. 色のコントラスト
Section titled “3. 色のコントラスト”WCAGコントラスト要件
Section titled “WCAGコントラスト要件”WCAG 2.1では、テキストと背景のコントラスト比が以下の要件を満たす必要があります。
コントラスト比の要件: ├─ 通常のテキスト(AA): 4.5:1以上 ├─ 大きなテキスト(AA): 3:1以上(18pt以上、または14pt以上の太字) └─ 通常のテキスト(AAA): 7:1以上問題のある実装:
/* ❌ 悪い例: コントラストが低い */.low-contrast { color: #cccccc; /* 薄いグレー */ background-color: #ffffff; /* 白 */ /* コントラスト比: 約1.6:1 */}改善された実装:
/* ✅ 良い例: 十分なコントラスト */.high-contrast { color: #333333; /* 濃いグレー */ background-color: #ffffff; /* 白 */ /* コントラスト比: 約12.6:1 */}コントラストチェックツール
Section titled “コントラストチェックツール”// コントラスト比の計算function getContrastRatio(color1: string, color2: string): number { const luminance1 = getLuminance(color1); const luminance2 = getLuminance(color2); const lighter = Math.max(luminance1, luminance2); const darker = Math.min(luminance1, luminance2); return (lighter + 0.05) / (darker + 0.05);}
// 使用例const contrast = getContrastRatio('#333333', '#ffffff');console.log(contrast); // 約12.64. 画像の代替テキスト
Section titled “4. 画像の代替テキスト”alt属性の適切な使用
Section titled “alt属性の適切な使用”<!-- ✅ 良い例: 意味のある画像には説明を提供 --><img src="chart.png" alt="2024年の売上は前年比120%増加">
<!-- ✅ 良い例: 装飾的な画像は空のalt属性 --><img src="decoration.png" alt="">
<!-- ✅ 良い例: 複雑な画像には詳細な説明 --><img src="diagram.png" alt="システムアーキテクチャ図"><details> <summary>詳細な説明</summary> <p>この図は、フロントエンド、API Gateway、マイクロサービス、データベースの関係を示しています。</p></details>背景画像の代替
Section titled “背景画像の代替”<!-- 背景画像を使用する場合の代替 --><div class="hero-image" role="img" aria-label="美しい山の風景" style="background-image: url('mountain.jpg')"> <h1>山の風景</h1></div>5. フォームのアクセシビリティ
Section titled “5. フォームのアクセシビリティ”ラベルの関連付け
Section titled “ラベルの関連付け”<!-- ✅ 良い例: label要素で関連付け --><label for="username">ユーザー名</label><input type="text" id="username" name="username">
<!-- ✅ 良い例: label要素で囲む --><label> メールアドレス <input type="email" name="email"></label>
<!-- ✅ 良い例: aria-labelを使用 --><input type="text" name="search" aria-label="検索キーワード" placeholder="検索...">エラーメッセージ
Section titled “エラーメッセージ”<!-- ✅ 良い例: aria-describedbyでエラーを関連付け --><label for="email">メールアドレス</label><input type="email" id="email" name="email" aria-invalid="true" aria-describedby="email-error"><span id="email-error" role="alert"> 有効なメールアドレスを入力してください</span>必須フィールド
Section titled “必須フィールド”<!-- ✅ 良い例: 必須フィールドの明示 --><label for="name"> 名前 <span aria-label="必須">*</span></label><input type="text" id="name" name="name" required aria-required="true">6. 動的コンテンツの対応
Section titled “6. 動的コンテンツの対応”ライブリージョン
Section titled “ライブリージョン”<!-- ✅ 良い例: aria-liveで動的コンテンツを通知 --><div aria-live="polite" aria-atomic="true"> <span id="status">読み込み中...</span></div>
<script> // ステータスが変更されたら自動的に読み上げられる document.getElementById('status').textContent = '読み込み完了';</script>ローディング状態
Section titled “ローディング状態”<!-- ✅ 良い例: ローディング状態の明示 --><button type="submit" aria-busy="true" aria-label="送信中..." disabled> <span class="spinner" aria-hidden="true"></span> <span class="sr-only">送信中...</span></button>7. スクリーンリーダー対応
Section titled “7. スクリーンリーダー対応”視覚的に隠すが読み上げる要素
Section titled “視覚的に隠すが読み上げる要素”/* スクリーンリーダー専用のクラス */.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0;}<!-- 使用例 --><button> <span class="icon" aria-hidden="true">×</span> <span class="sr-only">閉じる</span></button>aria-hidden属性
Section titled “aria-hidden属性”<!-- 装飾的な要素をスクリーンリーダーから隠す --><div aria-hidden="true"> <span class="icon">★</span> <span class="icon">★</span> <span class="icon">★</span></div><span class="sr-only">評価: 3つ星</span>8. モーダルダイアログ
Section titled “8. モーダルダイアログ”アクセシブルなモーダル
Section titled “アクセシブルなモーダル”<!-- ✅ 良い例: アクセシブルなモーダル --><div role="dialog" aria-modal="true" aria-labelledby="modal-title" aria-describedby="modal-description"> <h2 id="modal-title">確認</h2> <p id="modal-description">この操作を実行してもよろしいですか?</p> <button>OK</button> <button>キャンセル</button></div>フォーカストラップ
Section titled “フォーカストラップ”// モーダル内でフォーカスを閉じ込めるfunction trapFocus(modalElement: HTMLElement) { const focusableElements = modalElement.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' );
const firstElement = focusableElements[0] as HTMLElement; const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;
modalElement.addEventListener('keydown', (e) => { if (e.key === 'Tab') { if (e.shiftKey) { if (document.activeElement === firstElement) { lastElement.focus(); e.preventDefault(); } } else { if (document.activeElement === lastElement) { firstElement.focus(); e.preventDefault(); } } } });}9. テスト方法
Section titled “9. テスト方法”自動テストツール
Section titled “自動テストツール”# axe DevToolsnpm install -g @axe-core/cliaxe http://localhost:3000
# Lighthousenpm install -g lighthouselighthouse http://localhost:3000 --view
# Pa11ynpm install -g pa11ypa11y http://localhost:3000## アクセシビリティチェックリスト
### キーボード操作- [ ] Tabキーで全てのインタラクティブ要素にアクセスできる- [ ] EnterキーまたはSpaceキーで操作できる- [ ] Escキーでモーダルを閉じられる- [ ] フォーカス順序が論理的である
### スクリーンリーダー- [ ] スクリーンリーダーで全てのコンテンツが読み上げられる- [ ] 画像に適切な代替テキストがある- [ ] フォームに適切なラベルがある- [ ] エラーメッセージが適切に読み上げられる
### 視覚- [ ] 色だけで情報を伝えていない- [ ] コントラスト比が十分である- [ ] テキストサイズを拡大しても使用可能である
### その他- [ ] タイムアウトがある場合は延長できる- [ ] アニメーションを無効化できる- [ ] エラーが発生した場合、適切なメッセージが表示される10. WCAGガイドライン
Section titled “10. WCAGガイドライン”WCAG 2.1の4つの原則
Section titled “WCAG 2.1の4つの原則”1. Perceivable(知覚可能) - 情報とUIコンポーネントは、ユーザーが知覚できる方法で提示される
2. Operable(操作可能) - UIコンポーネントとナビゲーションは操作可能である
3. Understandable(理解可能) - 情報とUIの操作は理解可能である
4. Robust(堅牢) - コンテンツは、支援技術を含む様々なユーザーエージェントで解釈できるレベル別要件
Section titled “レベル別要件”レベルA(最低要件): - 基本的なアクセシビリティ要件 - すべてのWebサイトが満たすべき要件
レベルAA(推奨): - より高いアクセシビリティ要件 - 多くのWebサイトが目指すべき要件
レベルAAA(最高): - 最高レベルのアクセシビリティ要件 - すべての要件を満たすことは困難11. 実践的なベストプラクティス
Section titled “11. 実践的なベストプラクティス”コンポーネントライブラリの使用
Section titled “コンポーネントライブラリの使用”// React + Radix UIの例import * as Dialog from '@radix-ui/react-dialog';
function Modal({ title, children }) { return ( <Dialog.Root> <Dialog.Trigger>開く</Dialog.Trigger> <Dialog.Portal> <Dialog.Overlay /> <Dialog.Content> <Dialog.Title>{title}</Dialog.Title> {children} <Dialog.Close>閉じる</Dialog.Close> </Dialog.Content> </Dialog.Portal> </Dialog.Root> );}アクセシビリティチェックの自動化
Section titled “アクセシビリティチェックの自動化”{ "scripts": { "test:a11y": "pa11y-ci --sitemap http://localhost:3000/sitemap.xml" }, "devDependencies": { "pa11y-ci": "^3.0.1" }}12. よくある問題と解決方法
Section titled “12. よくある問題と解決方法”問題1: カスタムコンポーネントがキーボードで操作できない
Section titled “問題1: カスタムコンポーネントがキーボードで操作できない”// 解決: 適切な属性とイベントハンドラーを追加function CustomButton({ onClick, children }) { const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick(); } };
return ( <div role="button" tabIndex={0} onClick={onClick} onKeyDown={handleKeyDown} aria-label={children} > {children} </div> );}問題2: フォーカスが失われる
Section titled “問題2: フォーカスが失われる”// 解決: フォーカス管理function Modal({ isOpen, onClose, children }) { const modalRef = useRef<HTMLDivElement>(null);
useEffect(() => { if (isOpen && modalRef.current) { const firstFocusable = modalRef.current.querySelector( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ) as HTMLElement; firstFocusable?.focus(); } }, [isOpen]);
return ( <div ref={modalRef} role="dialog" aria-modal="true" > {children} </div> );}アクセシビリティ向上完全ガイドのポイント:
- セマンティックHTML: 意味のあるHTML要素を使用
- キーボード操作: すべての機能をキーボードで操作可能に
- 色のコントラスト: WCAG要件を満たすコントラスト比
- 画像の代替テキスト: 適切なalt属性の使用
- フォームのアクセシビリティ: ラベルとエラーメッセージの適切な関連付け
- 動的コンテンツ: ライブリージョンとローディング状態の明示
- スクリーンリーダー対応: aria属性の適切な使用
- モーダルダイアログ: フォーカストラップと適切な属性
- テスト方法: 自動テストと手動テスト
- WCAGガイドライン: レベルA、AA、AAAの要件
適切なアクセシビリティ対策により、すべてのユーザーが使用できるWebアプリケーションを構築できます。