accessibility について
accessibilityは、WCAG 2.1とLighthouseのアクセシビリティ監査ガイドに基づく、デザイナーや開発者向けのスキルです。アクセシビリティの原則や実装例に加え、自動テストと手動テストの方法を扱います。
主な機能
- 01WCAGのPOUR原則の適用
- 02コントラストとテキスト代替の確認
- 03キーボード操作可能なUI設計
- 04フォーカス・フォーム・エラーへの対応
- 05自動および手動チェックの実施
活用シーン
- 01WCAGガイドに沿ったページ監査
- 02キーボード操作とフォーカス表示の改善
- 03フォームラベルとエラー通知の確認
- 04アクセシビリティツールでページをテスト
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ davila7/ claude-code-templates --skill accessibilityインストール後、Claude Code で/accessibilityを入力して読み込まれたか確認します。
同梱ファイルreferences/WCAG.md
詳細
アクセシビリティ(a11y)
WCAG 2.1 と Lighthouse のアクセシビリティ監査に基づく包括的なガイドラインです。目標は、障害のある人を含め、すべての人がコンテンツを利用できるようにすることです。
WCAG の原則:POUR
| 原則 | 説明 |
|---|---|
| Perceivable(知覚可能) | コンテンツは様々な感覚を通じて知覚できる |
| Operable(操作可能) | インターフェースはすべてのユーザーが操作できる |
| Understandable(理解可能) | コンテンツとインターフェースが理解できる |
| Robust(堅牢) | コンテンツが支援技術で動作する |
適合レベル
| レベル | 要件 | 目標 |
|---|---|---|
| A | 最低限のアクセシビリティ | 必ず満たす |
| AA | 標準的な準拠 | 満たすべき(多くの国・地域で法的要件) |
| AAA | 拡張されたアクセシビリティ | できれば満たすとよい |
知覚可能
代替テキスト(1.1)
画像には代替テキストが必要です:
<img src="chart.png">
<img src="chart.png" alt="Bar chart showing 40% increase in Q3 sales">
<img src="decorative-border.png" alt="" role="presentation">
<figure>
<img src="infographic.png" alt="2024 market trends infographic"
aria-describedby="infographic-desc">
<figcaption id="infographic-desc">
</figcaption>
</figure>
アイコンボタンにはアクセシブルな名前が必要です:
<button><svg></svg></button>
<button aria-label="Open menu">
<svg aria-hidden="true"></svg>
</button>
<button>
<svg aria-hidden="true"></svg>
<span class="visually-hidden">Open menu</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) | 4.5:1 | 7:1 |
| 大きいテキスト(≥ 18px / 太字の場合 ≥ 14px) | 3:1 | 4.5:1 |
| UI コンポーネントとグラフィック | 3:1 | 3:1 |
/* ❌ Low contrast (2.5:1) */
.low-contrast {
color: #999;
background: #fff;
}
/* ✅ Sufficient contrast (7:1) */
.high-contrast {
color: #333;
background: #fff;
}
/* ✅ Focus states need contrast too */
:focus-visible {
outline: 2px solid #005fcc;
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>
Please enter a valid email address
</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>Transcript</summary>
<p>Full transcript text...</p>
</details>
操作可能
キーボードでの操作(2.1)
すべての機能がキーボードで操作できる必要があります:
// ❌ Only handles click
element.addEventListener('click', handleAction);
// ✅ Handles both click and keyboard
element.addEventListener('click', handleAction);
element.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleAction();
}
});
キーボードトラップを作らないでください:
// Modal focus management
function openModal(modal) {
const focusableElements = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
// Trap focus within modal
modal.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
if (e.key === 'Escape') {
closeModal();
}
});
firstElement.focus();
}
フォーカスの可視化(2.4.7)
/* ❌ Never remove focus outlines */
*:focus { outline: none; }
/* ✅ Use :focus-visible for keyboard-only focus */
:focus {
outline: none;
}
:focus-visible {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
/* ✅ Or custom focus styles */
button:focus-visible {
box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.5);
}
スキップリンク(2.4.1)
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<header></header>
<main id="main-content" tabindex="-1">
</main>
</body>
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px 16px;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
時間制限(2.2)
// Allow users to extend time limits
function showSessionWarning() {
const modal = createModal({
title: 'Session Expiring',
content: 'Your session will expire in 2 minutes.',
actions: [
{ label: 'Extend session', action: extendSession },
{ label: 'Log out', action: logout }
],
timeout: 120000 // 2 minutes to respond
});
}
動き(2.3)
/* Respect reduced motion preference */
@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>The French word for hello is <span lang="fr">bonjour</span>.</p>
一貫したナビゲーション(3.2.3)
<nav aria-label="Main">
<ul>
<li><a href="/" aria-current="page">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
フォームのラベル(3.3.2)
<input type="email" placeholder="Email">
<label for="email">Email address</label>
<input type="email" id="email" name="email"
autocomplete="email" required>
<label>
Email address
<input type="email" name="email" autocomplete="email" required>
</label>
<label for="password">Password</label>
<input type="password" id="password"
aria-describedby="password-requirements">
<p id="password-requirements">
Must be at least 8 characters with one number.
</p>
エラー処理(3.3.1、3.3.3)
<form novalidate>
<div class="field" aria-live="polite">
<label for="email">Email</label>
<input type="email" id="email"
aria-invalid="true"
aria-describedby="email-error">
<p id="email-error" class="error" role="alert">
Please enter a valid email address (e.g., [email protected])
</p>
</div>
</form>
// Focus first error on submit
form.addEventListener('submit', (e) => {
const firstError = form.querySelector('[aria-invalid="true"]');
if (firstError) {
e.preventDefault();
firstError.focus();
// Announce error summary
const errorSummary = document.getElementById('error-summary');
errorSummary.textContent = `${errors.length} errors found. Please fix them and try again.`;
errorSummary.focus();
}
});
堅牢
有効な HTML(4.1.1)
<div id="content">...</div>
<div id="content">...</div>
<a href="/"><button>Click</button></a>
<div id="main-content">...</div>
<div id="sidebar-content">...</div>
<a href="/" class="button-link">Click</a>
ARIA の使用(4.1.2)
ネイティブ要素を優先してください:
<div role="button" tabindex="0">Click me</div>
<button>Click me</button>
<div role="checkbox" aria-checked="false">Option</div>
<label><input type="checkbox"> Option</label>
ARIA が必要な場合:
<div role="tablist" aria-label="Product information">
<button role="tab" id="tab-1" aria-selected="true"
aria-controls="panel-1">Description</button>
<button role="tab" id="tab-2" aria-selected="false"
aria-controls="panel-2" tabindex="-1">Reviews</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
</div>
ライブリージョン(4.1.3)
<div aria-live="polite" aria-atomic="true" class="status">
</div>
<div role="alert" aria-live="assertive">
</div>
// Announce dynamic content changes
function showNotification(message, type = 'polite') {
const container = document.getElementById(`${type}-announcer`);
container.textContent = ''; // Clear first
requestAnimationFrame(() => {
container.textContent = message;
});
}
テストチェックリスト
自動テスト
# Lighthouse accessibility audit
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でテストする - フォーカス順序: 論理的で、視覚的な順序に従っている
スクリーンリーダーのコマンド
| 操作 | VoiceOver(Mac) | NVDA(Windows) |
|---|---|---|
| 開始/停止 | ⌘ + F5 | Ctrl + Alt + N |
| 次の項目 | VO + → | ↓ |
| 前の項目 | VO + ← | ↑ |
| 実行 | VO + Space | Enter |
| 見出しの一覧 | VO + U、その後矢印キー | H / Shift + H |
| リンクの一覧 | VO + U | K / Shift + K |
影響度別の一般的な問題
重大(直ちに修正)
- フォームラベルの欠落
- 画像の代替テキストの欠落
- 色のコントラスト不足
- キーボードトラップ
- フォーカスインジケーターがない
深刻(公開前に修正)
- ページの言語の欠落
- 見出し構造の欠落
- 内容がわかりにくいリンクテキスト
- 自動再生されるメディア
- スキップリンクの欠落
中程度(早めに修正)
- アイコンに ARIA ラベルがない
- 一貫性のないナビゲーション
- エラーの識別がない
- 制御手段のない時間制限
- ランドマークリージョンの欠落
参考資料
コメント
Markdown 対応