accessibility について
accessibility は、WCAG 2.1 AA を軸にした、ウェブデザイナーと開発者向けのフレームワーク非依存スキルです。セマンティック HTML、キーボード操作、コントラスト、フォーム、テストの指針を扱います。
主な機能
- 01セマンティック HTML と ARIA の指針
- 02キーボード操作とフォーカス管理
- 03WCAG AA の色コントラスト要件
- 04アクセシブルなフォームとエラーメッセージ
- 05キーボード・スクリーンリーダー・自動テスト
活用シーン
- 01ウェブサイトの WCAG 2.1 AA 適合確認
- 02ダイアログ、タブ、フォームのアクセシビリティ改善
- 03キーボードやスクリーンリーダー利用者向けの操作調整
- 04自動ツールによるアクセシビリティ問題の検査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jezweb/ claude-skills --skill accessibilityインストール後、Claude Code で/accessibilityを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.md.claude-plugin/plugin.jsonagents/a11y-auditor.mdreferences/aria-patterns.mdcolor-contrast.mdfocus-management.mdforms-validation.mdsemantic-html.mdwcag-checklist.mdrules/accessibility.md詳細
Web アクセシビリティ(WCAG 2.1 AA)
ステータス: 本番環境で利用可能 ✅ 最終更新日: 2026-01-14 依存関係: なし(フレームワーク非依存) 標準: WCAG 2.1 レベル AA
クイックスタート(5 分)
1. セマンティック HTML の基礎
適切な要素を選びましょう。すべてに div を使わないでください。
<div onclick="submit()">Submit</div>
<div onclick="navigate()">Next page</div>
<button type="submit">Submit</button>
<a href="/next">Next page</a>
重要な理由:
- セマンティック要素にはキーボード操作のサポートが組み込まれています
- スクリーンリーダーはロールを自動的に読み上げます
- ブラウザーは標準でアクセシブルな動作を提供します
2. フォーカス管理
操作可能な要素をキーボードでアクセスできるようにします。
/* ❌ WRONG - removes focus outline */
button:focus { outline: none; }
/* ✅ CORRECT - custom accessible outline */
button:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
重要:
- 代替手段を用意せずにフォーカスアウトラインを削除しないでください
:focus-visibleを使って、キーボードフォーカス時のみ表示します- フォーカスインジケーターのコントラスト比を 3:1 以上にします
3. テキストによる代替
テキスト以外のすべての要素には、テキストによる代替が必要です。
<img src="logo.png">
<button><svg>...</svg></button>
<img src="logo.png" alt="Company Name">
<button aria-label="Close dialog"><svg>...</svg></button>
アクセシビリティ対応の 5 ステップ
ステップ 1:セマンティック HTML を選ぶ
要素選択の判断ツリー:
Need clickable element?
├─ Navigates to another page? → <a href="...">
├─ Submits form? → <button type="submit">
├─ Opens dialog? → <button aria-haspopup="dialog">
└─ Other action? → <button type="button">
Grouping content?
├─ Self-contained article? → <article>
├─ Thematic section? → <section>
├─ Navigation links? → <nav>
└─ Supplementary info? → <aside>
Form element?
├─ Text input? → <input type="text">
├─ Multiple choice? → <select> or <input type="radio">
├─ Toggle? → <input type="checkbox"> or <button aria-pressed>
└─ Long text? → <textarea>
完全なガイドは references/semantic-html.md を参照してください。
ステップ 2:必要に応じて ARIA を追加する
基本原則:HTML でパターンを表現できない場合に限り、ARIA を使います。
<button role="button">Click me</button>
<div role="dialog" aria-labelledby="title" aria-modal="true">
<h2 id="title">Confirm action</h2>
</div>
<dialog aria-labelledby="title">
<h2 id="title">Confirm action</h2>
</dialog>
よく使われる ARIA パターン:
aria-label- 表示ラベルがない場合aria-labelledby- 既存のテキストをラベルとして参照する場合aria-describedby- 追加の説明aria-live- 動的な更新を読み上げる場合aria-expanded- 折りたたみ/展開状態
パターンの詳細は references/aria-patterns.md を参照してください。
ステップ 3:キーボード操作を実装する
すべての操作可能な要素は、キーボードでアクセスできなければなりません。
// Tab order management
function Dialog({ onClose }) {
const dialogRef = useRef<HTMLDivElement>(null);
const previousFocus = useRef<HTMLElement | null>(null);
useEffect(() => {
// Save previous focus
previousFocus.current = document.activeElement as HTMLElement;
// Focus first element in dialog
const firstFocusable = dialogRef.current?.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
(firstFocusable as HTMLElement)?.focus();
// Trap focus within dialog
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
if (e.key === 'Tab') {
// Focus trap logic here
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
// Restore focus on close
previousFocus.current?.focus();
};
}, [onClose]);
return <div ref={dialogRef} role="dialog">...</div>;
}
基本的なキーボード操作パターン:
- Tab/Shift+Tab:フォーカス可能な要素間を移動する
- Enter/Space:ボタン/リンクを実行する
- 矢印キー:コンポーネント内(タブ、メニュー)を移動する
- Escape:ダイアログ/メニューを閉じる
- Home/End:先頭/末尾の項目に移動する
パターンの詳細は references/focus-management.md を参照してください。
ステップ 4:色のコントラストを確保する
WCAG AA の要件:
- 通常のテキスト(18pt 未満):コントラスト比 4.5:1
- 大きなテキスト(18pt 以上、または太字で 14pt 以上):コントラスト比 3:1
- UI コンポーネント(ボタン、境界線):コントラスト比 3:1
/* ❌ WRONG - insufficient contrast */
:root {
--background: #ffffff;
--text: #999999; /* 2.8:1 - fails WCAG AA */
}
/* ✅ CORRECT - sufficient contrast */
:root {
--background: #ffffff;
--text: #595959; /* 4.6:1 - passes WCAG AA */
}
テストツール:
- ブラウザーの開発者ツール(Chrome/Firefox にはチェック機能が組み込まれています)
- コントラストチェッカーの拡張機能
- axe DevTools 拡張機能
完全なガイドは references/color-contrast.md を参照してください。
ステップ 5:フォームをアクセシブルにする
すべてのフォーム入力には、表示されるラベルが必要です。
<input type="email" placeholder="Email address">
<label for="email">Email address</label>
<input type="email" id="email" name="email" required aria-required="true">
エラー処理:
<label for="email">Email address</label>
<input
type="email"
id="email"
name="email"
aria-invalid="true"
aria-describedby="email-error"
>
<span id="email-error" role="alert">
Please enter a valid email address
</span>
動的なエラーに対するライブリージョン:
<div role="alert" aria-live="assertive" aria-atomic="true">
Form submission failed. Please fix the errors above.
</div>
パターンの詳細は references/forms-validation.md を参照してください。
重要なルール
必ず行うこと
✅ まずセマンティック HTML 要素(button、a、nav、article など)を使う
✅ テキスト以外のすべてのコンテンツに、テキストによる代替を用意する
✅ 通常のテキストは 4.5:1、大きなテキスト/UI は 3:1 のコントラスト比を確保する
✅ すべての機能をキーボードで操作できるようにする
✅ キーボードのみでテストする(マウスを取り外す)
✅ スクリーンリーダーでテストする(Windows では NVDA、Mac では VoiceOver)
✅ 適切な見出し階層を使う(h1 → h2 → h3。階層を飛ばさない)
✅ すべてのフォーム入力に、表示されるラベルを付ける
✅ フォーカスインジケーターを用意する(outline: none のみで済ませない)
✅ 動的コンテンツの更新には aria-live を使う
絶対にしないこと
❌ button の代わりに onClick を設定した div を使う
❌ 代替手段を用意せずにフォーカスアウトラインを削除する
❌ 情報を伝える手段として色だけを使う
❌ ラベルとしてプレースホルダーを使う
❌ 見出しレベルを飛ばす(h1 → h3)
❌ tabindex > 0 を使う(自然な順序が崩れます)
❌ セマンティック HTML で表現できる場合に ARIA を追加する
❌ ダイアログを閉じた後にフォーカスを戻し忘れる
❌ フォーカス可能な要素に role="presentation" を使う
❌ キーボードトラップを作る(抜け出す方法がない状態)
既知の問題の防止
このスキルは、文書化されているアクセシビリティの問題 12 件 を防止します。
問題 #1:フォーカスインジケーターがない
エラー:操作可能な要素に、視認できるフォーカスインジケーターがありません 出典:WCAG 2.4.7(フォーカスの可視化) 発生する理由:CSS リセットによってデフォルトのアウトラインが削除されます 防止策:常にカスタムの focus-visible スタイルを用意します
問題 #2:色のコントラストが不十分
エラー:テキストのコントラスト比が 4.5:1 未満です 出典:WCAG 1.4.3(コントラスト(最低限)) 発生する理由:白い背景に薄いグレーのテキストを使用しているため 防止策:コントラストチェッカーを使って、すべてのテキストの色を確認します
問題 #3:代替テキストがない
エラー:画像に alt 属性がありません 出典:WCAG 1.1.1(非テキストコンテンツ) 発生する理由:追加し忘れた、または任意だと思っていたため 防止策:装飾目的の画像には alt="" を、意味のある画像には内容を説明する alt を追加します
問題 #4:キーボード操作ができない
エラー:インタラクティブな要素にキーボードでフォーカスできません 出典:WCAG 2.1.1(キーボード) 発生する理由:button の代わりに div に onClick を使っているため 防止策:button や a など、セマンティックなインタラクティブ要素を使用します
問題 #5:フォーム入力欄にラベルがない
エラー:入力欄に対応するラベルがありません
出典:WCAG 3.3.2(ラベルまたは説明)
発生する理由:placeholder をラベルとして使っているため
防止策:必ず <label> 要素を使い、for/id で関連付けます
問題 #6:見出しレベルが飛んでいる
エラー:見出しの階層が h1 から h3 に飛んでいます 出典:WCAG 1.3.1(情報および関係性) 発生する理由:意味ではなく見た目を整えるために見出しを使っているため 防止策:見出しを順序どおりに使い、スタイルは CSS で設定します
問題 #7:ダイアログにフォーカストラップがない
エラー:Tab キーでダイアログから背後のコンテンツに移動できます 出典:WCAG 2.4.3(フォーカス順序) 発生する理由:フォーカストラップが実装されていないため 防止策:モーダルダイアログにフォーカストラップを実装します
問題 #8:動的コンテンツに aria-live がない
エラー:スクリーンリーダーが更新を通知しません 出典:WCAG 4.1.3(ステータスメッセージ) 発生する理由:通知なしで動的コンテンツが追加されているため 防止策:aria-live="polite" または "assertive" を使います
問題 #9:色だけで情報を伝えている
エラー:状態を色だけで伝えています 出典:WCAG 1.4.1(色の使用) 発生する理由:エラーを赤いテキストだけで示し、アイコンやテキストを併用していないため 防止策:色だけでなく、アイコンとテキストラベルを追加します
問題 #10:リンクテキストが不明確
エラー:「ここをクリック」や「続きを読む」というリンクがあります 出典:WCAG 2.4.4(リンクの目的) 発生する理由:文脈のない汎用的なリンクテキストを使っているため 防止策:内容がわかるリンクテキストまたは aria-label を使います
問題 #11:メディアが自動再生される
エラー:ユーザーが操作できない状態で動画や音声が自動再生されます 出典:WCAG 1.4.2(音声の制御) 発生する理由:controls を指定せずに autoplay 属性を使っているため 防止策:メディアの再生開始にユーザーの操作を必要とします
問題 #12:カスタムコントロールがアクセシブルでない
エラー:カスタムのセレクトボックスやチェックボックスをキーボードで操作できません 出典:WCAG 4.1.2(名前、役割、値) 発生する理由:ARIA を使わずに div で構築しているため 防止策:ネイティブ要素を使うか、ARIA パターンを完全に実装します
WCAG 2.1 AA クイックチェックリスト
知覚可能
- すべての画像に代替テキストがある(装飾目的の場合は alt="")
- テキストのコントラスト比が 4.5:1 以上(通常サイズ)、3:1 以上(大きな文字)
- 情報を色だけで伝えていない
- コンテンツを失うことなくテキストを 200% まで拡大できる
- 3 秒を超える音声が自動再生されない
操作可能
- すべての機能をキーボードで操作できる
- キーボードトラップがない
- フォーカスインジケーターが表示される
- 動くコンテンツを一時停止、停止、または非表示にできる
- ページタイトルで目的がわかる
- フォーカス順序が論理的である
- リンクの目的がテキストまたは文脈からわかる
- ページを見つける方法が複数ある(メニュー、検索、サイトマップ)
- 見出しとラベルで目的がわかる
理解可能
- ページの言語が指定されている(
<html lang="en">) - 言語の変更がマークされている(
<span lang="es">) - フォーカス時や入力時に、予期しないコンテキストの変化がない
- サイト全体でナビゲーションが一貫している
- フォームのラベルや説明がある
- 入力エラーが特定され、説明されている
- 法的事項、財務、データの変更についてエラーを防止できる
堅牢
- HTML が有効である(解析エラーがない)
- すべての UI コンポーネントで名前、役割、値を取得できる
- ステータスメッセージが識別されている(aria-live)
テストのワークフロー
1. キーボードのみでのテスト(5分)
1. Unplug mouse or hide cursor
2. Tab through entire page
- Can you reach all interactive elements?
- Can you activate all buttons/links?
- Is focus order logical?
3. Use Enter/Space to activate
4. Use Escape to close dialogs
5. Use arrow keys in menus/tabs
2. スクリーンリーダーでのテスト(10分)
NVDA(Windows - 無料):
- ダウンロード:https://www.nvaccess.org/download/
- 起動:Ctrl+Alt+N
- 移動:矢印キーまたはTab
- 読み上げ:NVDA+Down arrow
- 停止:NVDA+Q
VoiceOver(Mac - 標準搭載):
- 起動:Cmd+F5
- 移動:VO+Right/Left arrow(VO = Ctrl+Option)
- 読み上げ:VO+A(すべて読み上げ)
- 停止:Cmd+F5
テスト項目:
- すべてのインタラクティブ要素が読み上げられますか?
- 画像の説明は適切ですか?
- フォームのラベルは入力欄と一緒に読み上げられますか?
- 動的な更新は読み上げられますか?
- 見出しの構造は明確ですか?
3. 自動テスト
axe DevTools(ブラウザー拡張機能 - 強く推奨):
- インストール:Chrome/Firefox 拡張機能
- 実行:F12 → axe DevTools タブ → Scan
- 修正:違反内容を確認し、修正方法に従います
- 再テスト:修正後に再度スキャンします
Lighthouse(Chrome に標準搭載):
- DevTools を開きます(F12)
- Lighthouse タブを開きます
- 「Accessibility」カテゴリを選択します
- レポートを生成します
- スコア90以上が良好で、100が理想です
よくあるパターン
パターン 1:アクセシブルなダイアログ/モーダル
interface DialogProps {
isOpen: boolean;
onClose: () => void;
title: string;
children: React.ReactNode;
}
function Dialog({ isOpen, onClose, title, children }: DialogProps) {
const dialogRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!isOpen) return;
const previousFocus = document.activeElement as HTMLElement;
// Focus first focusable element
const firstFocusable = dialogRef.current?.querySelector(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
) as HTMLElement;
firstFocusable?.focus();
// Focus trap
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
if (e.key === 'Tab') {
const focusableElements = dialogRef.current?.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (!focusableElements?.length) return;
const first = focusableElements[0] as HTMLElement;
const last = focusableElements[focusableElements.length - 1] as HTMLElement;
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
previousFocus?.focus();
};
}, [isOpen, onClose]);
if (!isOpen) return null;
return (
<>
{/* Backdrop */}
<div
className="dialog-backdrop"
onClick={onClose}
aria-hidden="true"
/>
{/* Dialog */}
<div
ref={dialogRef}
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
className="dialog"
>
<h2 id="dialog-title">{title}</h2>
<div className="dialog-content">{children}</div>
<button onClick={onClose} aria-label="Close dialog">×</button>
</div>
</>
);
}
使用する場面:背後のコンテンツとの操作を妨げるモーダルダイアログやオーバーレイ全般。
パターン 2:アクセシブルなタブ
function Tabs({ tabs }: { tabs: Array<{ label: string; content: React.ReactNode }> }) {
const [activeIndex, setActiveIndex] = useState(0);
const handleKeyDown = (e: React.KeyboardEvent, index: number) => {
if (e.key === 'ArrowLeft') {
e.preventDefault();
const newIndex = index === 0 ? tabs.length - 1 : index - 1;
setActiveIndex(newIndex);
} else if (e.key === 'ArrowRight') {
e.preventDefault();
const newIndex = index === tabs.length - 1 ? 0 : index + 1;
setActiveIndex(newIndex);
} else if (e.key === 'Home') {
e.preventDefault();
setActiveIndex(0);
} else if (e.key === 'End') {
e.preventDefault();
setActiveIndex(tabs.length - 1);
}
};
return (
<div>
<div role="tablist" aria-label="Content tabs">
{tabs.map((tab, index) => (
<button
key={index}
role="tab"
aria-selected={activeIndex === index}
aria-controls={`panel-${index}`}
id={`tab-${index}`}
tabIndex={activeIndex === index ? 0 : -1}
onClick={() => setActiveIndex(index)}
onKeyDown={(e) => handleKeyDown(e, index)}
>
{tab.label}
</button>
))}
</div>
{tabs.map((tab, index) => (
<div
key={index}
role="tabpanel"
id={`panel-${index}`}
aria-labelledby={`tab-${index}`}
hidden={activeIndex !== index}
tabIndex={0}
>
{tab.content}
</div>
))}
</div>
);
}
使用する場面:複数のパネルがあるタブ付きインターフェース。
パターン 3:スキップリンク
<a href="#main-content" class="skip-link">
Skip to main content
</a>
<style>
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: var(--primary);
color: white;
padding: 8px 16px;
z-index: 9999;
}
.skip-link:focus {
top: 0;
}
</style>
<main id="main-content" tabindex="-1">
</main>
使用する場面:メインコンテンツの前にナビゲーションやヘッダーがある複数ページ構成のウェブサイト全般。
パターン 4:バリデーション機能付きのアクセシブルなフォーム
function ContactForm() {
const [errors, setErrors] = useState<Record<string, string>>({});
const [touched, setTouched] = useState<Record<string, boolean>>({});
const validateEmail = (email: string) => {
if (!email) return 'Email is required';
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return 'Email is invalid';
return '';
};
const handleBlur = (field: string, value: string) => {
setTouched(prev => ({ ...prev, [field]: true }));
const error = validateEmail(value);
setErrors(prev => ({ ...prev, [field]: error }));
};
return (
<form>
<div>
<label htmlFor="email">Email address *</label>
<input
type="email"
id="email"
name="email"
required
aria-required="true"
aria-invalid={touched.email && !!errors.email}
aria-describedby={errors.email ? 'email-error' : undefined}
onBlur={(e) => handleBlur('email', e.target.value)}
/>
{touched.email && errors.email && (
<span id="email-error" role="alert" className="error">
{errors.email}
</span>
)}
</div>
<button type="submit">Submit</button>
{/* Global form error */}
<div role="alert" aria-live="assertive" aria-atomic="true">
{/* Dynamic error message appears here */}
</div>
</form>
);
}
使用する場面:バリデーション機能のあるフォーム全般。
同梱リソースの使用
リファレンス(references/)
詳細な調査に役立つドキュメント:
- wcag-checklist.md - WCAG 2.1 レベル A および AA の要件を、例とともに網羅
- semantic-html.md - 要素の選択ガイドと、各タグの使い分け
- aria-patterns.md - ARIA のロール、状態、プロパティと使い方
- focus-management.md - フォーカス順序、フォーカストラップ、フォーカス復帰のパターン
- color-contrast.md - コントラスト要件、テストツール、カラーパレットのヒント
- forms-validation.md - アクセシブルなフォームのパターン、エラー処理、読み上げ
Claude がこれらを読み込む場面:
- ユーザーが WCAG チェックリストの全項目を求めている場合
- 特定のパターン(タブ、アコーディオンなど)を詳しく調べる場合
- 色のコントラストの問題やパレットの設計について扱う場合
- 複雑なフォームのバリデーションシナリオを扱う場合
エージェント(agents/)
- a11y-auditor.md - ページの違反をチェックする自動アクセシビリティ監査エージェント
使用する場面:既存のページ/コンポーネントのアクセシビリティ監査を依頼された場合。
高度なトピック
ARIA ライブリージョン
3つの通知レベル:
<div aria-live="polite">New messages: 3</div>
<div aria-live="assertive" role="alert">
Error: Form submission failed
</div>
<div aria-live="off">Loading...</div>
ベストプラクティス:
- 通知やカウンターなど、重要度の低い更新には
politeを使用します - エラーや重大な警告には
assertiveを使用します - 変更時にリージョン全体を読み上げるには
aria-atomic="true"を使用します - メッセージは簡潔で意味のあるものにします
SPA でのフォーカス管理
React Router はナビゲーション時にフォーカスをリセットしないため、手動で処理する必要があります:
function App() {
const location = useLocation();
const mainRef = useRef<HTMLElement>(null);
useEffect(() => {
// Focus main content on route change
mainRef.current?.focus();
// Announce page title to screen readers
const title = document.title;
const announcement = document.createElement('div');
announcement.setAttribute('role', 'status');
announcement.setAttribute('aria-live', 'polite');
announcement.textContent = `Navigated to ${title}`;
document.body.appendChild(announcement);
setTimeout(() => announcement.remove(), 1000);
}, [location.pathname]);
return <main ref={mainRef} tabIndex={-1} id="main-content">...</main>;
}
アクセシブルなデータテーブル
<table>
<caption>Monthly sales by region</caption>
<thead>
<tr>
<th scope="col">Region</th>
<th scope="col">Q1</th>
<th scope="col">Q2</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">North</th>
<td>$10,000</td>
<td>$12,000</td>
</tr>
</tbody>
</table>
主な属性:
<caption>- テーブルの目的を説明しますscope="col"- 列見出しを識別しますscope="row"- 行見出しを識別します- スクリーンリーダーがデータセルと見出しを関連付けられるようにします
公式ドキュメント
- WCAG 2.1:https://www.w3.org/WAI/WCAG21/quickref/
- MDN アクセシビリティ:https://developer.mozilla.org/en-US/docs/Web/Accessibility
- ARIA オーサリングプラクティス:https://www.w3.org/WAI/ARIA/apg/
- WebAIM:https://webaim.org/articles/
- axe DevTools:https://www.deque.com/axe/devtools/
トラブルシューティング
問題:フォーカスインジケーターが表示されない
症状:ページ内を Tab キーで移動できますが、フォーカス位置がわかりません 原因:CSS によってアウトラインが削除されているか、コントラストが不十分です 解決策:
*:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
問題:スクリーンリーダーが更新を読み上げない
症状:動的コンテンツが変化しても読み上げられません
原因:aria-live リージョンがありません
解決策:動的コンテンツを <div aria-live="polite"> で囲むか、role="alert" を使用します
問題:ダイアログから背景へフォーカスが移動する
症状:Tab キーを押すと、ダイアログの背後にある要素に移動します 原因:フォーカストラップがありません 解決策:フォーカストラップを実装します(上記のパターン 1 を参照してください)
問題:フォームのエラーが読み上げられない
症状:画面にはエラーが表示されますが、スクリーンリーダーが認識しません 原因:aria-invalid または role="alert" がありません 解決策:aria-invalid と aria-describedby を使用し、role="alert" を指定したエラーメッセージを参照します
セットアップの完全チェックリスト
すべてのページ/コンポーネントで使用してください。
- すべてのインタラクティブ要素がキーボードで操作できる
- すべてのフォーカス可能な要素に視認できるフォーカスインジケーターがある
- 画像に alt テキストがある(装飾目的の場合は alt="")
- テキストのコントラスト比が 4.5:1 以上である(axe または Lighthouse でテスト)
- フォーム入力に関連付けられたラベルがある(プレースホルダーだけではない)
- 見出しの階層が論理的である(レベルを飛ばしていない)
- ページに
<html lang="en">または適切な言語が指定されている - ダイアログにフォーカストラップがあり、閉じたときにフォーカスが元に戻る
- 動的コンテンツで aria-live または role="alert" を使用している
- 情報を伝える手段として色だけに頼っていない
- キーボードのみでテスト済み(マウスなし)
- スクリーンリーダー(NVDA または VoiceOver)でテスト済み
- axe DevTools のスキャンを実行済み(違反 0 件)
- Lighthouse のアクセシビリティスコアが 90 以上
質問や問題がありますか?
- 完全な要件については
references/wcag-checklist.mdを確認してください /a11y-auditorエージェントを使用してページをスキャンしてください- axe DevTools を実行して自動テストを行ってください
- 実際のキーボードとスクリーンリーダーでテストしてください
標準:WCAG 2.1 レベル AA テストツール:axe DevTools、Lighthouse、NVDA、VoiceOver 成功基準:Lighthouse スコア 90 以上、重大な違反 0 件