カラーコントラスト比チェッカー
テキストと背景色間のWCAG AA・AAAコントラスト比をチェック
前景色(テキスト)
背景色
大きいテキスト見本(18px太字)
通常テキスト見本 — 選択した背景色に対してボディテキストがどのように表示されるかを確認できます。
コントラスト比
14.63:1
WCAG準拠
適合
通常 AA
適合
通常 AAA
適合
大きい AA
適合
大きい AAA
WCAG AA — 通常テキスト: 4.5:1, 大きいテキスト(18px以上または14px太字): 3:1
WCAG AAA — 通常テキスト: 7:1, 大きいテキスト(18px以上または14px太字): 4.5:1
カラーコントラストチェックとは?
カラーコントラストチェックは、前景色(通常はテキスト)と背景色の輝度差を計測し、その結果を比率として表します。1:1は2色が同一であることを意味し、21:1は黒地に白(またはその逆)を表す最大値です。W3CによるWebコンテンツアクセシビリティガイドライン(WCAG)は、弱視や色覚異常を持つユーザーが読み取れるよう、テキストが満たすべき最小コントラスト比を定めています。
コントラスト比の計算式はWCAG 2.xに由来し、相対輝度(人間の目に色がどれほど明るく見えるかを表す指標)を使用します。相対輝度は、sRGBの各チャンネルを線形化(ガンマ除去)し、ITU-R BT.709の係数で重み付けして算出します。係数は赤が0.2126、緑が0.7152、青が0.0722です。人間の目は緑の光に最も敏感なため、緑の寄与が最も大きくなります。コントラスト比は (L1 + 0.05) / (L2 + 0.05)(L1は明るい色の輝度)で算出されます。
WCAGは2つの適合レベルを定義しています。レベルAAは、通常サイズのテキストに4.5:1以上、大きいテキスト(18px以上、または14px太字)に3:1以上のコントラスト比を要求します。レベルAAAはそれをさらに引き上げ、それぞれ7:1と4.5:1を要求します。また、WCAG 2.1では達成基準1.4.11が追加され、ボーダー・アイコン・フォーカスインジケーターなど、テキスト以外のUI要素にも3:1の比率が要求されています。
このコントラストチェッカーを使う理由
目視でコントラストを確認するのは信頼性が低い方法です。キャリブレーション済みのディスプレイでくっきり見える色も、低品質なノートパソコンの画面や直射日光下では区別しにくくなる場合があります。また、第二色覚異常(緑色盲)の方にとっても同様です。数値で表された比率は推測を排除し、WCAG基準に対する明確な適合・不適合の判定を与えます。
コントラストチェッカーのユースケース
WCAGコントラスト比の要件
下の表は、WCAG 2.1においてさまざまなコンテンツの種類と適合レベルで要求される最小コントラスト比をまとめたものです。大きいテキストとは、通常の太さで18px(24 CSSピクセル)以上、または太字で14px(18.66 CSSピクセル)以上と定義されています。
| レベル | 最小比率 | 適用対象 | 備考 |
|---|---|---|---|
| AA Normal text | 4.5 : 1 | Body text, paragraphs, labels | Minimum for most UI text |
| AA Large text | 3.0 : 1 | Text >= 18px, or >= 14px bold | Minimum for headings |
| AA UI components | 3.0 : 1 | Borders, icons, focus indicators | Non-text contrast (WCAG 1.4.11) |
| AAA Normal text | 7.0 : 1 | Body text at highest standard | Enhanced readability |
| AAA Large text | 4.5 : 1 | Large text at highest standard | Enhanced for headings |
相対輝度・AA・AAAの解説
コントラスト比の計算には3段階あります。各色の輝度を計算し、比率を導き出し、WCAGのしきい値と比較します。
コード例
WCAGコントラスト比をプログラムで計算します。各例はWCAG 2.xの相対輝度計算式とコントラスト比の計算を実装しています。比較しやすいよう、黒地に白とインディゴ地に白の2つのカラーペアをテストしています。
// Calculate relative luminance per WCAG 2.x (sRGB)
function luminance(r, g, b) {
const [rs, gs, bs] = [r, g, b].map(c => {
c /= 255
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
})
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs
}
// Contrast ratio between two RGB colors
function contrastRatio(fg, bg) {
const l1 = luminance(...fg)
const l2 = luminance(...bg)
const lighter = Math.max(l1, l2)
const darker = Math.min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
}
contrastRatio([0, 0, 0], [255, 255, 255]) // -> 21.0
contrastRatio([99, 102, 241], [255, 255, 255]) // -> 3.95
contrastRatio([29, 78, 216], [255, 255, 255]) // -> 6.06 (AA pass)def luminance(r: int, g: int, b: int) -> float:
"""Relative luminance per WCAG 2.x, ITU-R BT.709 coefficients."""
channels = []
for c in (r, g, b):
c /= 255
channels.append(c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4)
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2]
def contrast_ratio(fg: tuple, bg: tuple) -> float:
l1 = luminance(*fg)
l2 = luminance(*bg)
lighter, darker = max(l1, l2), min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
print(f"{contrast_ratio((0, 0, 0), (255, 255, 255)):.2f}") # -> 21.00
print(f"{contrast_ratio((99, 102, 241), (255, 255, 255)):.2f}") # -> 3.95
# Check WCAG AA for normal text
ratio = contrast_ratio((29, 78, 216), (255, 255, 255))
print(f"{ratio:.2f} — {'AA pass' if ratio >= 4.5 else 'AA fail'}") # -> 6.06 — AA passpackage main
import (
"fmt"
"math"
)
func linearize(c float64) float64 {
c /= 255
if c <= 0.04045 {
return c / 12.92
}
return math.Pow((c+0.055)/1.055, 2.4)
}
func luminance(r, g, b int) float64 {
return 0.2126*linearize(float64(r)) +
0.7152*linearize(float64(g)) +
0.0722*linearize(float64(b))
}
func contrastRatio(fgR, fgG, fgB, bgR, bgG, bgB int) float64 {
l1 := luminance(fgR, fgG, fgB)
l2 := luminance(bgR, bgG, bgB)
lighter := math.Max(l1, l2)
darker := math.Min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
}
func main() {
ratio := contrastRatio(0, 0, 0, 255, 255, 255)
fmt.Printf("%.2f\n", ratio) // -> 21.00
ratio = contrastRatio(29, 78, 216, 255, 255, 255)
fmt.Printf("%.2f\n", ratio) // -> 6.06 (AA pass for normal text)
}/* WCAG-safe color pairs — tested contrast ratios */
/* 12.63:1 — passes AAA normal text */
.high-contrast {
color: #1e293b; /* slate-800 */
background: #f8fafc; /* slate-50 */
}
/* 7.07:1 — passes AAA normal text */
.dark-theme-text {
color: #e2e8f0; /* slate-200 */
background: #0f172a; /* slate-900 */
}
/* 4.57:1 — passes AA normal, fails AAA */
.accent-on-white {
color: #1d4ed8; /* blue-700 */
background: #ffffff;
}
/* 2.14:1 — fails AA for text, but passes 3:1 for large text */
.muted-heading {
color: #94a3b8; /* slate-400 */
background: #ffffff;
}