- Tech
- HTML
- JavaScript
WCAG達成基準で見るアクセシビリティ実装 ― NG例とOK例から学ぶ

はじめに
多くのエンジニアにとって、アクセシビリティ対応は「視覚障害者向けの特別な対応」というイメージが先行しがちです。でも実際に対象となるのは、メガネを忘れた人、電車の揺れでスマートフォンの操作がしづらい人、イヤホンを忘れて動画の音声が聞けない人など、障害の有無にかかわらず幅広い人たちです。
この記事では、以下の観点からアクセシビリティに取り組む意味を考えてみます。
- SEO対策との重なり:ページのタイトルや、画像に付ける説明文(alt属性)を適切に設定することは、検索エンジンにとっても、画面読み上げソフト(スクリーンリーダー)にとっても、内容を正しく伝えることにつながります
- さまざまなデバイスへの対応:特殊な操作機器や、色数の少ない画面でも使えるように作ることは、結果として幅広い利用環境に対応することになります
- 使いやすさ全体の土台:アクセシビリティが低いと、その上に成り立つはずのUX(利用しやすさ・安心感・満足度)にも影響が出てしまいます
こうした背景を踏まえた上で、実装者が具体的に何を確認すればいいのかを、WCAGの達成基準に沿って整理していきます。
a11yとは何か
アクセシビリティ(accessibility)は、access(利用できること)と ability(能力)を組み合わせた言葉です。「a11y」という略称は、a と y の間に文字が11個あることに由来します。
WCAGとは何か
WCAG(Web Content Accessibility Guidelines)は、W3Cが策定するWebアクセシビリティのガイドラインです。1999年に1.0が策定されて以降、モバイル対応や弱視・認知障害への配慮を加えながら2.1(2018年)、現行最新の2.2(2023年、86個の達成基準を含む)へと改訂が重ねられてきました。
達成基準は、次の四原則に分類されます。
- 知覚可能:情報や画面の構成要素を、利用者が知覚できる形で提示できているか
- 操作可能:画面やメニューを、実際に操作できるか
- 理解可能:情報や操作の仕方を、理解できるか
- 堅牢:さまざまな支援技術を含め、幅広い環境で正しく解釈できるように作られているか
適合レベルにはA・AA・AAAの3段階があり、上位レベルは下位レベルの基準もすべて満たしています。
JIS X 8341との関係
日本国内の規格であるJIS X 8341-3:2016は、WCAG 2.0と同じ内容の規格です。一方、本記事で扱うWCAG 2.2には、WCAG 2.0以降に追加された達成基準なども含まれているため、JIS X 8341-3:2016と同一の内容ではありません。 ちなみに「8341」は、「やさしい」の語呂合わせとされています。
達成基準ごとのNG例とOK例
ここからは、実装のときに特に問題になりやすい達成基準を取り上げて、NG例とOK例を並べて紹介します。
コントラスト比
文字と背景の色の差(コントラスト比)が一定以上あることが求められます。目安として、通常の文字は4.5:1以上、アイコンなどの非テキスト要素は3:1以上とされています(レベルAA)。訪問済みリンク、見出しの文字、選択肢の状態表示などは、淡い色使いによってこの基準を下回りやすい箇所です。実装する側でこれに気づいた場合は、デザインの色設定を見直す提案材料になります。
// NG: 背景に対して文字色が薄く、コントラスト比が4.5:1を下回る
.text {
color: #aaaaaa;
background-color: #ffffff;
}
// OK: コントラスト比 4.5:1 以上を確保
.text {
color: #595959;
background-color: #ffffff;
}
色だけに頼らない
色の違いだけで情報を伝えないようにする、という基準です。よくあるNG例が色分けされた円グラフで、色の見分け方が一般的な人と異なる利用者(色覚特性を持つ人)には、複数の部分が同じ色に見えてしまうことがあります。色の見分け方には個人差があるため、色の組み合わせを工夫するだけではうまくいかないことも多いです。OK例としては、引き出し線でどの部分かをはっきり示す、数値を書き添えて色を見なくても内容がわかるようにする、といった対応が挙げられます。
<!-- NG: 色の違いだけで必須項目を示している -->
<label style="color: red;">メールアドレス</label>
<input type="email" name="email">
<!-- OK: 色に加えて文字でも必須であることを示す -->
<label for="email">メールアドレス <span class="required">(必須)</span></label>
<input type="email" id="email" name="email" required aria-required="true">
リンク・ボタンの目的
リンクやボタンの文字だけを見て、押すとどこに行くのか、何が起きるのかがわかる必要があります。「詳しく見る」「詳しくはこちら」といったラベルはよくあるNG例で、前後の文脈を目で追える人にしか意味が伝わりません。確認方法としては、スクリーンリーダーのリンク一覧表示機能を使って、リンクの文字だけを並べた状態で意味が通じるかを見る方法があります。OK例は「〇〇の詳細を見る」のように、対象を明示したラベルにすることです。
<!-- NG: リンク単体では何の詳細かわからない -->
<h3>料金プランA</h3>
<p>月額980円で利用できるプランです。</p>
<a href="/plans/a">詳しく見る</a>
<!-- OK: リンクテキスト単独で対象がわかる -->
<h3>料金プランA</h3>
<p>月額980円で利用できるプランです。</p>
<a href="/plans/a">料金プランAの詳細を見る</a>
<!-- OK: 視覚的な見た目を変えずに対応したい場合、aria-labelで補足する手もある -->
<a href="/plans/a" aria-label="料金プランAの詳細を見る">詳しく見る</a>
フォーカスの可視化
キーボード操作可能な画面には、今どこを操作しているか(フォーカス)を視覚的に示す印が必要です。outline: none などでこの印を消してしまうことがよくありますが、そうするとキーボードを使う利用者が今どこにいるのかわからなくなってしまいます。フォーカスの印を消す場合は、必ず代わりのスタイルを用意しましょう。
// NG: フォーカスの印を消したまま代替スタイルがない
button:focus {
outline: none;
}
// OK: 見た目を整えつつ、フォーカスの印を別の形で残す
button:focus-visible {
outline: none;
box-shadow: 0 0 0 3px #1a73e8;
}
キーボード操作
すべての機能を、マウスを使わずキーボードだけで操作できるようにする必要があります。
よくあるNG例は、div要素にクリックイベントだけを付けた作り方で、この場合はフォーカスが当たらず、キーボードでは操作できません。
button要素を使えば、キーボード操作とフォーカス表示は標準機能として得られます。
一方role=”button”は要素の役割を支援技術に伝えるだけの属性で、フォーカスやキーボード操作は付与されません。tabindex=”0″の追加と、Enter・Spaceキー押下時の処理を自前で実装する必要があります。
CSSでタグ名に直接スタイルを指定していると、あとから正しい要素に置き換える際の影響範囲が広くなりやすいので、この点も実装方針として気に留めておきたいところです。
<!-- NG: divにクリック処理だけを付けただけでフォーカスが当たらない -->
<div>送信</div>
<!-- JavaScript側でクリックイベントのみを付与している状態 -->
<!-- NG: role="button"だけではフォーカスもキーボード操作も得られない -->
<div role="button">送信</div>
<!-- tabindexもキーボードイベント処理も未実装の状態 -->
<!-- OK: button要素を使うことでキーボード操作とフォーカスが標準で得られる -->
<button type="button">送信</button>
適切なalt属性
見た目を整えるためだけの装飾画像には、alt属性を空にしておきます。画像の中に文字が書かれている場合は、その文字をそのまま代替テキストにします。画像がリンクになっている場合は、リンク全体としてどこに行くのかが自然に伝わるよう代替テキストを調整します。
<!-- NG: 装飾画像であることをalt自体で説明してしまっている -->
<img src="deco_line_01.png" alt="装飾用の線画像">
<!-- OK: 装飾目的の画像はaltを空にする -->
<img src="deco_line_01.png" alt="">
<!-- NG: 画像内テキストの内容がaltに反映されていない -->
<img src="banner_sale.png" alt="バナー">
<!-- OK: 画像内のテキストをそのまま代替テキストにする -->
<img src="banner_sale.png" alt="全品20%オフセール開催中">
<!-- OK: 画像がリンクの場合、リンク全体としての行き先を示す -->
<a href="/sale">
<img src="banner_sale.png" alt="セール詳細ページへ">
</a>
適切な見出し
見出しはh1からh6までの要素を、文章の構成に沿って意味的に使います。文字を大きく強調したいという見た目上の理由だけで見出し要素を使うのは避けましょう。
<!-- NG: 見た目を大きくしたいだけでh2を使っている -->
<h2>※このキャンペーンは予告なく終了する場合があります</h2>
<!-- OK: 強調したいだけならCSSで見た目を調整する -->
<p class="notice">※このキャンペーンは予告なく終了する場合があります</p>
<!-- OK: 見出しは文章構成に沿って使う -->
<h1>料金プラン</h1>
<h2>プランA</h2>
<h2>プランB</h2>
適切なラベル・aria-label
画面に表示されているラベルと、スクリーンリーダーが読み上げる内容(アクセシブルネーム)は、一致させることが基本です。ただし、画面には表示されず読み上げソフトにだけ伝わる説明文(aria-label)を使う場面では、もう一段の注意が必要です。aria-labelは実装しても画面上には見えないため、目で見て確認するだけのレビューでは内容の間違いに気づきにくくなります。
<!-- NG: アイコンのみのボタンにラベルがなく、読み上げ内容がない -->
<button onclick="closeModal()">
<svg><!-- ×アイコン --></svg>
</button>
<!-- OK: aria-labelで読み上げ内容を補う -->
<button onclick="closeModal()" aria-label="閉じる">
<svg><!-- ×アイコン --></svg>
</button>
<!-- NG: 表示されているラベルと読み上げ内容が食い違っている -->
<button aria-label="送信する">キャンセル</button>
<!-- OK: 表示ラベルと読み上げ内容を一致させる -->
<button>キャンセル</button>
アニメーションの抑制
自動的に開始し、5秒を超えて継続する動き・点滅・スクロールなどには、必要不可欠な場合を除き、一時停止・停止・非表示にできる仕組みが必要です。
これに加えて、prefers-reduced-motionというメディアクエリに対応し、利用者が「動きを減らす」設定をしている場合はそれに従う実装にしておくのが望ましいです。ただしこれは上記の一時停止・停止などの操作を代替するものではなく、別途対応が必要な要件です。
// NG: 常に同じアニメーションを適用し、停止方法や設定への配慮がない
.banner {
animation: slide 3s infinite;
}
// OK: 動きを減らす設定をしている利用者には抑えた表示にする
.banner {
animation: slide 3s infinite;
}
@media (prefers-reduced-motion: reduce) {
.banner {
animation: none;
}
}
エンジニア単独では解決できない部分
上記の達成基準の多くは、標準的なHTML要素やARIA属性を正しく使うことで対応できます。ただ、すべてがエンジニアリングだけで完結するわけではありません。
まず、aria-labelやリンクテキストの文言そのものは、何をどう伝えるべきかという判断が伴います。この判断は本質的に文章づくりに関連する内容で、エディターやライターが関わったほうがうまくいく領域です。
加えて、aria-labelは画面上に見えないため、目で見て確認するだけのレビューでは動作の不備に気づけません。次のような観点でもチェックが必要です。
- 実際にスクリーンリーダーで読み上げられるか(実装した通りに動作するか)
- 読み上げは正しいのか(誤読や読み上げ漏れがないか)
- 文言は適切な内容なのか(伝えるべき情報が過不足なく含まれているか)
これらは、実装した本人が目視で確認するだけでは担保できません。読み上げる文章を書くこと自体に加えて、それが意図通りに動作するかを確認する工程を、実装のフローに別途組み込んでおく必要があります。
次に、色の使い方やコントラスト、フォーカスの印の見た目は、デザインシステムそのものに関わってきます。色分けに頼らない情報設計や、十分なコントラストを確保した配色は、デザイナーが設計段階から関わる必要があります。逆に、デザインが確定してから実装側だけで帳尻を合わせようとすると、直す範囲が広くなりがちです。
さらに、見た目の配置とDOM(画面の内部的な構造)の順序を一致させる作業も、デザインとエンジニアリング双方の合意が必要です。見た目の配置だけを優先してDOM構造を組むと、スクリーンリーダーでの読み上げ順序が不自然になる、という問題が起きやすくなります。
こうした背景から、「これは特定の職能の責務ではなく、制作チーム全体で取り組むべき課題である」ということがわかります。アクセシビリティ対応は実装フェーズだけで解決しようとせず、設計、デザインやライティングの工程から、WCAGの観点で検討することで、後からの手戻りを減らすことにつながります。
まとめ
アクセシビリティ対応の多くは、特別な技術ではなく、標準的なHTML要素とARIA属性を素直に使うことで実現できます。独自実装を増やすほど、キーボード操作やフォーカス制御を自前で作り込む必要が出てきて、抜け漏れも増えやすくなります。まずは標準に沿った実装を徹底することが、いちばん費用対効果の高い出発点です。
一方で、aria-labelの文言やデザインシステムの色使いのように、実装だけでは完結しない領域があるのも事実です。こうした観点をあらかじめ関係者間で共有しておくことが、後になって大きな手戻りが発生する事態を防ぐことにつながります。
参考・出典
- WCAG 2.2 日本語訳
https://waic.jp/translations/WCAG22/ - JIS X 8341-3:2016 解説
https://waic.jp/docs/jis2016/understanding/201604/ - WAI-ARIA Authoring Practices Guide
https://www.w3.org/WAI/ARIA/apg/practices/read-me-first/ - WCAG 2.2 達成基準2.2.2「一時停止、停止、非表示」日本語訳
https://waic.jp/translations/WCAG22/Understanding/pause-stop-hide.html
ドコドア エンジニア部
このブログでは、アプリ開発の現場で培ったフロントエンド、バックエンド、インフラ構築の知識から生成AI活用のノウハウまで、実践的な情報をアプリ開発に悩む皆様へ向けて発信しています!
【主な技術スタック】 Flutter / Firebase / Svelte / AWS / GCP / OpenAI API