フロントエンドアクセシビリティ
<ui-packages-dir> 内のコンポーネントと <web-dir>/src/ 内のページに適用されます。ターゲットは WCAG 2.1 Level AA であり、これは目標ではなく最低基準です。
プロジェクト規約の解決
山括弧内の値はプロジェクトのスタックプロファイルから取得します。次の順序で解決します:
- リポジトリルートにある
STACK-PROFILE.mdを読み、その値をそのまま使用します。 - ファイルがないかキーが欠けている場合は、リポジトリから推測します —
css-strategyとui-frameworkはWebアプリケーションの依存関係から取得します。 - それでも解決できない場合は、リポジトリ内ですでに支配的な規約に合わせます。
- リポジトリが空の場合のみ、このスキルに規定されたデフォルトにフォールバックします — その後、選択した値を
STACK-PROFILE.mdに書き込みます。
リポジトリがすでに規約を表現している場合、新しい規約を発明してはいけません。
厳格なルール
1. セマンティックHTMLファースト
- アクションには
<button>、ナビゲーションには<a>を使用します。クリックハンドラを持つ<div>は禁止です。 - 見出しレベルはスキップせずに降順で使用します。
- すべてのフォームフィールドに
<label>を付け、htmlForで関連付けます。 - グループ化されたコントロールには
<fieldset>と<legend>を使用します。 - ランドマーク —
<nav>、<main>、<footer>、<aside>— をすべてのページに配置します。
2. ARIAはHTMLでできない場合のみ
優先順位はセマンティック要素、次にARIA属性、最後にカスタム動作です。要素がすでに持つセマンティクスを上書きするためにARIAを使用してはいけません — <div> に role="button" を付けるのは、<button> が無料で提供するものを拙劣に再実装するだけです。
誤ったARIAはないより悪いです: 間違った aria-label は、スクリーンリーダーが本来正しく計算するアクセシブルな名前を静かに置き換えてしまいます。
3. キーボード操作は必須
- すべてのインタラクティブな要素がTabキーで到達可能であること。
- Tab順序は視覚的な順序に従うこと。
- フォーカスは常に可視であること。アウトラインを削除する場合は必ず代替を設定すること。
Escapeでダイアログとポップオーバーを閉じ、EnterとSpaceでコントロールをアクティブにし、矢印キーで複合ウィジェット内を移動します。- カスタムウィジェットは明示的にフォーカスを管理します。
4. 色とコントラスト
- 通常テキストは4.5:1、大きなテキスト(18px、または14pxの太字)および意味のあるUI境界線は3:1。
- 色だけに意味を持たせてはいけません — テキスト、アイコン、またはパターンと組み合わせます。
- デフォルトのテーマだけでなく、両方のテーマをチェックします。
5. 画像とメディア
- すべての画像に
alt属性を付けます。コンテンツ画像は説明的に、装飾画像はalt=""とします。 - 「画像」「写真」、またはファイル名をaltテキストに使ってはいけません。
- 複雑な画像には、
aria-describedbyまたはキャプションを通じて長い説明を提供します。 - 動画にはキャプション、音声にはトランスクリプトを付けます。
6. フォーム
- すべての入力にラベルを付けます。
- エラーは
aria-describedbyでリンクし、role="alert"でアナウンスします。 - 無効なフィールドには
aria-invalidをマークします。 - 必須フィールドには
required属性を付けます — アスタリスクを表示する場合、その意味はどこかにテキストで記載します。 - 送信失敗時は、最初の無効なフィールドにフォーカスを移動します。
7. ターゲットサイズ
WCAG 2.1 AA には最小ターゲットサイズはありません(2.2 AA では24×24 CSS pxを設定; 44×44は2.1 AAAおよびプラットフォームHIGの慣例です)。タッチサーフェス上のプライマリアクションでは44×44を下限として扱い、次のように適用します:
- プライマリおよびデストラクティブなアクション、およびユーザーが電話で触れるもの: 44×44。
- 高密度なデスクトップクローム — ツールバー、テーブル行アクション、コンパクトなフォームコントロール — は、ターゲットが他のターゲットに隣接しておらず、同等のフルサイズのコントロールが別の場所に存在する場合、24×24まで小さくしても構いません。
- 間隔も重要です。32pxのコントロールでも、各側に6pxの明確なスペースがあれば2.2の基準を満たしますが、隣接するコントロールに接触している32pxのコントロールは満たしません。
ベンダー製コンポーネントライブラリは通常この下限を下回って出荷されます — デフォルトのボタンの高さが32または36 CSS pxであることは一般的です。ベンダーファイルを手動で編集して修正してはいけません。また、デフォルトで不一致を受け入れてはいけません。<wrappers-pkg> でデフォルトを引き上げ、すべてのコンシューマーが準拠したサイズを継承するようにし、小さなサイズは高密度なコンテキスト用の明示的なオプトインとして利用できるようにします。そうすることで、アクセシブルな選択肢が、考えずに使用した場合のデフォルトになります。
プロジェクトがどの解決策を採用したかを STACK-PROFILE.md に記録します。表明されていない例外は、半年ごとにバグとして再発見されます。
8. モーション
prefers-reduced-motionを尊重します。- 自動再生コンテンツには一時停止コントロールが必要です。
- 毎秒3回以上の点滅は禁止です。
9. テストは作業の一部
- キーボードだけで機能を操作します。
- スクリーンリーダーで聞きます — macOSではVoiceOver、WindowsではNVDA。
- エンドツーエンドスイートで自動スキャンを実行します。
自動化ツールは実際の問題の約3分の1を検出します。これらは合格基準ではなく、下限です。
パターン
完全な実装は references/component-patterns.md にあります。各パターンが保証する内容:
- ボタン — セマンティックな
<button>で、フォーカスリングがキーボードユーザーには表示されるがマウスクリックでは表示されない(focus-visible)、モーションセーフなトランジション、アイコンのみの使用にはariaLabelプロパティ。 - フォーム入力 — ラベル付き入力で、エラーテキストが
aria-describedbyでプログラム的にリンクされ、role="alert"でアナウンスされ、aria-invalidでフラグが立てられる; 必須アスタリスクはaria-hidden。 - ダイアログ — ネイティブの
<dialog>を使用し、showModal()で無料のフォーカストラップ、Escape処理、およびバックグラウンドの inert を実現; 開くときにフォーカスが移動し、閉じるとトリガーに戻る。 - リストボックス — カスタムセレクトで、矢印キーによる巡回ナビゲーションと正しいARIA配線(
aria-activedescendant、role="listbox"); ネイティブの<select>が適さない場合のみ構築する。 - スキップリンク — キーボードユーザーがナビゲーションをスキップできるようにし、フォーカスされたときのみ表示; ターゲットは
tabIndex={-1}を持ち、フォーカスが実際にそこに移動する。
WCAG クイックリファレンス
| 原則 | 要件 | 実際の対応 |
|---|---|---|
| 知覚可能 | コンテンツがすべての感覚で利用可能 | Altテキスト、キャプション、色+アイコン |
| 操作可能 | すべてがキーボードで到達可能 | Tab順序、Escapeで閉じる、フォーカス可視 |
| 理解可能 | 予測可能、明確な表現 | 一貫したナビゲーション、平易な言語 |
| 堅牢 | 支援技術と連携 | 有効なHTML、正しいARIA、セマンティック要素 |
| 問題 | 修正 |
|---|---|
| ラベルなし入力 | <label htmlFor> を追加 |
| 低コントラスト | 通常テキスト4.5:1、大きなテキスト3:1を満たす |
| キーボード経路なし | 実際のインタラクティブ要素、フォーカス管理 |
| alt欠落 | 説明する、または装飾の場合は alt="" |
| 色のみの意味 | テキストまたはアイコンを追加 |
| アイコンのみのボタン | aria-label を追加 |
| 点滅コンテンツ | 毎秒3回未満に抑える |
モーション設定
プレーンなCSSで縮小モーションを処理します。motion-safe: のようなユーティリティクラスバリアントは、モーションが許可されている場合にのみ適用されるため、縮小モーションクエリ内に配置すると、決してマッチしないルールになります。
@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;
}
}
ゼロに近い値(noneではない)にすることで、トランジション終了イベントが発生し続け、それを待つコンポーネントがハングしません。
コンポーネントごとの制御には、アニメーションユーティリティ自体に motion-safe: バリアントを適用します — 上記のボタンのように motion-safe:transition-colors のようにします。
視覚的に隠されたコンテンツ
ほとんどのユーティリティフレームワークはすでに sr-only と not-sr-only を提供しています。それらを使用し、再定義しないでください。提供されていない場合、参照実装は references/component-patterns.md にあります。
テストチェックリスト
キーボード
- すべてのインタラクティブ要素がTabで到達可能
- Tab順序が視覚的な順序と一致
- フォーカスが常に可視
- Escapeでダイアログとポップオーバーを閉じる
- 矢印キーが複合ウィジェットで機能する
- ダイアログが閉じるとフォーカスがトリガーに戻る
スクリーンリーダー
- すべてのコンテンツに到達可能
- コントロールの目的が周囲のコンテキストなしで明確
- ラベルがフィールドとともにアナウンスされる
- エラーが
role="alert"を通じてアナウンスされる - ランドマークが存在する
- 装飾要素が
aria-hiddenで隠されている
色
- 通常テキスト4.5:1、大きなテキストとUI境界線3:1
- 色のみの意味なし
- 両方のテーマを確認
モーション
- 縮小モーションが尊重されている
- 毎秒3回以上の点滅なし
- 自動再生コンテンツが一時停止可能
自動化
スキャンはPlaywrightスイート内で実行されます — frontend-testing を参照。Playwright内のAxeスニペットは references/component-patterns.md にあります。
参照ドキュメント
| 詳細 | 参照 |
|---|---|
| アクセシブルコンポーネント実装: ボタン、フォーム入力、モーダル、リストボックス、スキップリンク | references/component-patterns.md |
関連スキル
frontend-architecture— コンポーネントの階層とテーマ設定frontend-testing— これらのチェックが実行される場所frontend-component-docs— コンポーネントのアクセシビリティ契約の文書化