Skip to content

アクセシビリティ向上完全ガイド

アクセシビリティ向上完全ガイド

Section titled “アクセシビリティ向上完全ガイド”

Webアプリケーションのアクセシビリティを向上させる実践的な方法を、実務で使える実装例とベストプラクティスとともに詳しく解説します。

セマンティック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>
<!-- 構造的な要素 -->
<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>

すべてのインタラクティブ要素はキーボードで操作可能である必要があります。

問題のある実装:

<!-- ❌ 悪い例: キーボードで操作できない -->
<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>
<!-- フォーカス可能な要素 -->
<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>
/* フォーカスインジケーターのスタイル */
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);
}

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 */
}
// コントラスト比の計算
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.6
<!-- ✅ 良い例: 意味のある画像には説明を提供 -->
<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>
<!-- 背景画像を使用する場合の代替 -->
<div
class="hero-image"
role="img"
aria-label="美しい山の風景"
style="background-image: url('mountain.jpg')"
>
<h1>山の風景</h1>
</div>

5. フォームのアクセシビリティ

Section titled “5. フォームのアクセシビリティ”
<!-- ✅ 良い例: 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="検索..."
>
<!-- ✅ 良い例: 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>
<!-- ✅ 良い例: 必須フィールドの明示 -->
<label for="name">
名前
<span aria-label="必須">*</span>
</label>
<input
type="text"
id="name"
name="name"
required
aria-required="true"
>
<!-- ✅ 良い例: aria-liveで動的コンテンツを通知 -->
<div aria-live="polite" aria-atomic="true">
<span id="status">読み込み中...</span>
</div>
<script>
// ステータスが変更されたら自動的に読み上げられる
document.getElementById('status').textContent = '読み込み完了';
</script>
<!-- ✅ 良い例: ローディング状態の明示 -->
<button
type="submit"
aria-busy="true"
aria-label="送信中..."
disabled
>
<span class="spinner" aria-hidden="true"></span>
<span class="sr-only">送信中...</span>
</button>

視覚的に隠すが読み上げる要素

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>
<!-- 装飾的な要素をスクリーンリーダーから隠す -->
<div aria-hidden="true">
<span class="icon"></span>
<span class="icon"></span>
<span class="icon"></span>
</div>
<span class="sr-only">評価: 3つ星</span>
<!-- ✅ 良い例: アクセシブルなモーダル -->
<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>
// モーダル内でフォーカスを閉じ込める
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();
}
}
}
});
}
Terminal window
# axe DevTools
npm install -g @axe-core/cli
axe http://localhost:3000
# Lighthouse
npm install -g lighthouse
lighthouse http://localhost:3000 --view
# Pa11y
npm install -g pa11y
pa11y http://localhost:3000
## アクセシビリティチェックリスト
### キーボード操作
- [ ] Tabキーで全てのインタラクティブ要素にアクセスできる
- [ ] EnterキーまたはSpaceキーで操作できる
- [ ] Escキーでモーダルを閉じられる
- [ ] フォーカス順序が論理的である
### スクリーンリーダー
- [ ] スクリーンリーダーで全てのコンテンツが読み上げられる
- [ ] 画像に適切な代替テキストがある
- [ ] フォームに適切なラベルがある
- [ ] エラーメッセージが適切に読み上げられる
### 視覚
- [ ] 色だけで情報を伝えていない
- [ ] コントラスト比が十分である
- [ ] テキストサイズを拡大しても使用可能である
### その他
- [ ] タイムアウトがある場合は延長できる
- [ ] アニメーションを無効化できる
- [ ] エラーが発生した場合、適切なメッセージが表示される
1. Perceivable(知覚可能)
- 情報とUIコンポーネントは、ユーザーが知覚できる方法で提示される
2. Operable(操作可能)
- UIコンポーネントとナビゲーションは操作可能である
3. Understandable(理解可能)
- 情報とUIの操作は理解可能である
4. Robust(堅牢)
- コンテンツは、支援技術を含む様々なユーザーエージェントで解釈できる
レベル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 “アクセシビリティチェックの自動化”
package.json
{
"scripts": {
"test:a11y": "pa11y-ci --sitemap http://localhost:3000/sitemap.xml"
},
"devDependencies": {
"pa11y-ci": "^3.0.1"
}
}

問題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>
);
}
// 解決: フォーカス管理
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アプリケーションを構築できます。