推測に頼らず WCAG の色コントラストを満たす

コントラスト比が何を測っているのか、テキストと UI コンポーネントに対する AA と AAA のしきい値、そして毎回静かに不合格になる白地にグレーという失敗について解説します。

通常の本文テキストについて、WCAG のレベル AA は背景に対して少なくとも 4.5:1 のコントラスト比を求めます。大きなテキストは 3:1 です。UI コンポーネントと意味を持つグラフィックも 3:1 です。レベル AAA ではテキストが 7:1、大きなテキストが 4.5:1 に引き上げられます。比は 1:1 (まったく同じ色) から 21:1 (純白の上の純黒) までの範囲を取ります。

これは目で判断できません。手元のモニターで同じくらい読みやすく見える 2 つの組み合わせが、しきい値の反対側に位置していることもあります。ですから比は計算しなければなりません。

しきい値

対象AAAAA
通常のテキスト4.5:17:1
大きなテキスト3:14.5:1
UI コンポーネントとグラフィックオブジェクト3:1これ以上の基準なし

これらは 3 つの達成基準に由来します。AA の 1.4.3 コントラスト (最低限)、AAA の 1.4.6 コントラスト (高度)、そして AA の 1.4.11 非テキストのコントラストです。最後のものは、コントロールの視覚的な境界と、内容を理解するために必要なグラフィックの部分を対象としています。

大きなテキスト とは 18 ポイント以上、太字なら 14 ポイント以上を指し、おおよそ通常の太さで 24px、太字で 18.5px 程度に相当します。「見出し」という意味ではありません。20px の見出しは規則の上では通常のテキストであり、4.5:1 を満たす必要があります。

いくつかのものは免除されます。純粋に装飾的なテキスト、無効状態のコントロール内のテキスト、他に重要な視覚的内容を含む画像の一部となっているテキスト、そしてロゴタイプです。プレースホルダーや補助テキストは免除されません。

この比が実際に測っているもの

この式は 2 色の 相対輝度 を比較します。

ratio = (L_lighter + 0.05) / (L_darker + 0.05)

相対輝度は、その色がどれだけ光を発しているかを 0 (黒) から 1 (白) までの数値で表したものです。各チャンネルにかかっている sRGB のガンマ曲線を元に戻し、それらに重みを付けて算出します。

L = 0.2126 R + 0.7152 G + 0.0722 B     (on linearised channels)

両辺に加えられている 0.05 は、画面で反射するわずかな環境光をモデル化したものです。最大値が無限大ではなく 21 になるのは、このためです。

ここから 2 つのことが導かれます。1 つは、この比が輝度だけに依存し、色相には依存しないということです。そのため、色相がまったく違う 2 色でも、互いに重ねるとほとんど見えないことがあります。もう 1 つは、この比が対称だということです。どちらの色を前景と呼ぶかは結果に影響しません。

相対輝度は明るさの感覚ではない

先ほどの重みを見てください。輝度への寄与は緑が約 72%、赤が約 21%、青はわずか 7% です。これは人間の視覚のモデルであって、色がどれだけ鮮やかに見えるかのモデルではありません。そのため、慣れるまでは間違っているように感じる結果が出てきます。

  • 白地の上の純黄色 #FFFF00 は約 1.07:1 で、ほとんど見えません。黄色は強い色に感じられるにもかかわらずです。
  • 同じ黄色を黒地に置くと約 19.6:1 となり、黒地に白とほぼ変わらない高さになります。
  • 黒地の上の純青 #0000FF は約 2.4:1 で、どの基準も満たしません。力強く彩度が高く見えるにもかかわらずです。

HSL の明度が当てにならない代用指標であるのも、同じ理由です。HSL では明度 50% にある 2 色でも輝度は大きく異なりえます。HSL は色相と明度を独立に扱いますが、視覚はそうではないからです。明度を 1 段変えればコントラストも 1 段変わると仮定せず、変換してから測定してください。

繰り返される失敗

白地にグレーの本文。 これが最大の問題です。白地で AA を通る最も明るいグレーはおよそ #767676 で、4.54:1 になります。1 段明るい #777777 は 4.48:1 で不合格です。よく使われる「控えめな」グレー、たとえば #999999 は 2.9:1 前後で、必要な値の 3 分の 2 にも届きません。白地で AAA を狙うなら、およそ #595959 が必要で、これがちょうど 7:1 です。

プレースホルダーと補助テキスト。 二次的な要素だという理由で、本文より数段明るく設定されがちです。それでもテキストであることに変わりはなく、4.5:1 が必要です。

半透明の色。 要素の不透明度が 60% なら、宣言した色から求めた比には意味がありません。画面に実際に現れる合成後の色を求めて、それを測ってください。

画像、グラデーション、動画の上のテキスト。 比は画素ごとに変わり、問題になるのは最悪の画素です。写真が暗いままであることを期待するのではなく、テキストの背後に半透明の下地を敷くのが一般的な対処です。

ブランドカラーのボタン。 中間調のブランドカラーに白文字という組み合わせは 3:1 と 4.5:1 の間に落ちることが非常に多く、グラフィックオブジェクトとしては合格でもラベルとしては不合格になります。ボタンを暗くするか、不合格であることを承知の上で受け入れてください。

フォーカスインジケーターとアイコンだけのボタン。 どちらも非テキストの 3:1 の規則の対象です。同系色をわずかに変えただけのフォーカスリングはよくある見落としですし、ボタンの唯一の内容がグレーのアイコンという場合も同様です。

反転によるダークモード。 明るいパレットをひっくり返しても比は保たれません。輝度は中間を軸に対称ではないからです。純黒の上の純白は最大の 21:1 になりますが、そのハレーションを不快に感じる人は多く、そのため非常に暗いグレーの上にオフホワイトを置く、たとえば #121212 の上に #E6E6E6 でおよそ 15:1 という妥協がよく使われます。

スクリーンショットからの測定。 アンチエイリアスのかかったテキストの縁でスポイトを使うと、拾えるのはテキストの色ではなく、テキストと背景の混色です。太い線の中央から取るか、もっと良いのは CSS から値を読むことです。

推測を避ける進め方

まず背景を決め、次に前景をしきい値に少し余裕をもって収まるまで暗くするか明るくし、その組み合わせを記録します。パレットは承認された色ではなく、承認された組み合わせから組み立ててください。色は単独ではアクセシブルになりえず、組み合わせにおいてのみアクセシブルになります。通常状態だけでなくすべての状態を確認し、ダークモードを提供するなら両方のテーマを確認してください。

最後に 1 つ。より新しい知覚モデルである APCA が WCAG の将来のバージョンに向けて開発されており、特に暗い背景に明るいテキストという場合に、かなり異なる振る舞いをします。知っておく価値はありますが、今日準拠すべき数値は上に挙げた WCAG 2 の比です。