コントラスト比「4.50:1」なのにWCAG AAで不合格になる理由
文字色と背景色のコントラスト比が「4.50 : 1」と表示されているのに、WCAG の AA(通常テキストは 4.5:1 以上)では不合格になる。表示の誤りではなく、WCAG の規則どおりの結果である。
ハシトシステムの コントラスト比チェッカー は、比を小数2桁で表示し、合否は丸める前の値で判定している。この記事では、公開中のツールの関数を Node.js で取り出して、白背景の 16,777,216 色すべてについて「表示は 4.50 なのに不合格」になる色を数える。あわせて、相対輝度の式にある閾値 0.03928 と 0.04045 の違いが結果を変えるかも確かめる。
WCAG は「比を丸めずに比べる」と書いている
W3C の Understanding WCAG 2.2(達成基準 1.4.3 コントラスト(最低限))は、3:1 と 4.5:1 を閾値として扱い、計算した比は丸めずに比べるとしている。例として、4.499:1 は 4.5:1 を満たさないと明記している。
つまり、表示用に小数2桁へ丸めた「4.50」で判定してはいけない。ツールのコードも、表示と判定を分けている。
1 | |
toFixed(2) は 4.495 以上 4.5 未満を「4.50」と表示する。この範囲の色は、表示が 4.50 で判定が FAIL になる。
白背景で「4.50 なのに不合格」は 9,074 色
Node.js v24.11.1 で、ツールの ratio 関数に白(#ffffff)と全 1,677 万色を渡した結果が次のとおり(コードは後ろの「実際に試す」にある)。
| 文字色(白背景) | 比(丸める前) | 表示 | AA 通常テキスト(4.5) | AA 大きい文字(3) |
|---|---|---|---|---|
| #777777 | 4.478089 | 4.48 : 1 | FAIL | PASS |
| #767676 | 4.542225 | 4.54 : 1 | PASS | PASS |
| #006ffb | 4.499888 | 4.50 : 1 | FAIL | PASS |
| #0099ff | 2.999789 | 3.00 : 1 | FAIL | FAIL |
| 条件(白背景・全 16,777,216 色) | 色の数 |
|---|---|
| 表示が「4.50」で AA 通常テキスト不合格(4.495 以上 4.5 未満) | 9,074 |
| 表示が「3.00」で AA 大きい文字不合格(2.995 以上 3 未満) | 13,242 |
白背景のグレーで AA を満たす最も明るい色は #767676 で、#777777 は届かない。青系の #006ffb は比が 4.499888 で、小数2桁では 4.50 と表示されるが不合格になる。デザインツールや別のチェッカーが比を丸めて表示していて、「4.50 だから合格」と読むと、この範囲の色を通してしまう。
数字の見た目に頼らない対策は2つある。判定する側は、丸めた文字列ではなく丸める前の数値で >= 4.5 と比べる。色を決める側は、4.5 ちょうどを狙わず、少し余裕を持った色を選ぶ。
0.03928 と 0.04045 は 8bit の色では結果を変えない
相対輝度は、sRGB の各チャンネル(0〜255 を 255 で割った値)を線形の値に戻してから重み付きで足す。戻す式は、値が閾値以下なら c / 12.92、それより大きければ ((c + 0.055) / 1.055) ^ 2.4 である。
この閾値は資料によって 0.03928 と 0.04045 の2通りが出てくる。WCAG 2.2 の用語集の相対輝度の定義は現在 0.04045 で、注記に、2021年5月より前は 0.03928 だったこと、この変更はガイドラインの文脈では計算に実質的な影響がないことが書かれている。CSS Color Module Level 4 の sRGB の定義も 0.04045 を使っている。ツールのコードは古い値の 0.03928 のままである。
影響がない理由は、8bit の色で閾値の間に入る値が無いからである。
| 8bit の値 | 255 で割った値 | 0.03928 以下か | 0.04045 以下か |
|---|---|---|---|
| 10 | 0.03922 | はい | はい |
| 11 | 0.04314 | いいえ | いいえ |
10 と 11 の間に 0.03928 と 0.04045 の両方が入るので、0〜255 のどの値でもどちらの閾値でも同じ式が選ばれる。実際に 256 値すべてで線形化した結果を比べると、違いは 0 件だった。16bit の色や、0〜1 の小数で色を扱う場合は、0.03928 と 0.04045 の間に入る値があり得るので、現行の 0.04045 を使っておくのが無難である。
大きい文字の 18pt と 14pt は px でいくつか
AA の閾値は、大きい文字なら 3:1 に下がる。WCAG の大きい文字は、18pt 以上、または太字の 14pt 以上である。CSS では 1pt = 96/72 px なので、18pt は 24px、14pt は 18.667px になる。Understanding WCAG 2.2 は 14pt を「およそ 18.5px」と書いており、ツールの FAQ は 18.66px と書いている。境目の太字 18px や 18.5px の文字は 14pt に届かないので、大きい文字としては扱えない。
実際に試す
Node.js 18 以降(グローバルの fetch を使うため。記事では v24.11.1)で試せる。外部パッケージは使わない。全 1,677 万色のループがあるので、終わるまで少し待つ。
1 | |
1 | |
ページのコードが変われば結果も変わるので、2026-11-05 時点の結果として読んでほしい。背景を白以外にしたいときは、W の値を変える。
ハシトシステムのコントラスト比チェッカーでは
コントラスト比チェッカー は、文字色と背景色を HEX で入れると、比と4つの合否(AA / AAA × 通常テキスト / 大きい文字)をブラウザの中だけで出す。2026-11-05 時点のコードでは次のように動く。
- 比は小数2桁で表示し、合否は丸める前の値で判定する。#006ffb を白背景で入れると「4.50 : 1」と表示され、AA 通常テキストは FAIL になる。表示が 4.50 や 3.00 ちょうどのときは、合否の表示のほうを見る。
- 相対輝度の閾値は旧来の 0.03928 だが、HEX(8bit)の入力では現行の 0.04045 と結果が変わらない。
#RGBの3桁の HEX も受け付け、#777は#777777として計算する。
QR コードの色で読めるかどうかを相対輝度から考える話は 赤と黒の QR コードが読めない理由の記事 に書いた。QR コードの配色は QRコード 配色チェッカー で、色の HEX と RGB の変換は カラーコード変換 で確かめられる。
まとめ
- WCAG のコントラスト比は丸めずに閾値と比べる。4.499:1 は 4.5:1 を満たさない。
- 小数2桁の表示では 4.495 以上 4.5 未満が「4.50」になる。白背景だけで 9,074 色が「4.50 なのに AA 不合格」、13,242 色が「3.00 なのに大きい文字の AA 不合格」だった。
- 白背景のグレーで AA を満たす最も明るい色は #767676。#777777 は 4.48 で届かない。
- 相対輝度の閾値は現行の WCAG 2.2 では 0.04045(2021年5月より前は 0.03928)。8bit の色では 10/255 と 11/255 の間に両方が入るので、どちらでも結果は同じ。
- 大きい文字は 18pt(24px)以上か、太字 14pt(18.667px)以上。
参考
- W3C Web Content Accessibility Guidelines (WCAG) 2.2 — Glossary: relative luminance、large scale (text)
- W3C Understanding WCAG 2.2 — Understanding Success Criterion 1.4.3: Contrast (Minimum)
- W3C CSS Color Module Level 4 — 10.2 The Predefined sRGB Color Space