Skip to main content
🛡️ Verified Technical Content: Written by Serhii Hrekov. | Last reviewed & updated in Git: July 21, 2026

Contrast Checker: How to Calculate Color Contrast in Python

· 6 min read
Serhii Hrekov
Senior Software Engineer & System Architect specializing in Python, Web Systems, Cloud Infrastructure & Automation

Designing a beautiful UI is pointless if half your users can't read it. Whether it's a person with a visual impairment or someone trying to check their phone on a sunny day, color contrast is the secret sauce of accessible design.

The WCAG (Web Content Accessibility Guidelines) provides a mathematical way to ensure text stands out against its background. Let's integrate a "Contrast Checker" into our Python toolkit.


The Math of "Readability"

To calculate contrast, we first have to calculate Relative Luminance. Humans perceive green as much brighter than blue, so we use a weighted formula based on the sRGB color space.

1. Gamma Correction

Before calculating luminance, we must "linearize" the RGB values (which are usually stored in a non-linear way).

For each color $C \in \{R, G, B\}$:

$$C_{srgb} = \frac{C_{8bit}}{255}$$

$$C_{linear} = \begin{cases} \frac{C_{srgb}}{12.92}, & \text{if } C_{srgb} \leq 0.03928 \\ \left(\frac{C_{srgb} + 0.055}{1.055}\right)^{2.4}, & \text{otherwise} \end{cases}$$

2. The Luminance Formula

Once linearized, we apply the weights:

$$L = 0.2126 \cdot R_{linear} + 0.7152 \cdot G_{linear} + 0.0722 \cdot B_{linear}$$

3. The Contrast Ratio

Finally, the ratio is calculated by comparing the lighter color ($L_1$) and the darker color ($L_2$):


$$CR = \frac{L_1 + 0.05}{L_2 + 0.05}$$

This ratio will be a number between 1:1 (no contrast) and 21:1 (maximum contrast, like black on white).


Python Implementation

This code takes two colors (HEX or Name) and tells you exactly how they perform against accessibility standards.

import webcolors

def get_luminance(color_input):
"""Calculates relative luminance of a color."""
# Convert input to RGB
if color_input.startswith('#'):
rgb = webcolors.hex_to_rgb(color_input)
else:
rgb = webcolors.name_to_rgb(color_input)

# Linearize and Gamma Correct
rgb_list = [rgb.red / 255, rgb.green / 255, rgb.blue / 255]
linear_rgb = []
for c in rgb_list:
if c <= 0.03928:
linear_rgb.append(c / 12.92)
else:
linear_rgb.append(((c + 0.055) / 1.055) ** 2.4)

# Calculate weighted luminance
r, g, b = linear_rgb
return 0.2126 * r + 0.7152 * g + 0.0722 * b

def check_contrast(foreground, background):
l1 = get_luminance(foreground)
l2 = get_luminance(background)

# Ensure l1 is the lighter color
if l1 < l2:
l1, l2 = l2, l1

ratio = (l1 + 0.05) / (l2 + 0.05)

print(f"--- 👓 Accessibility Report ---")
print(f"Colors: {foreground} on {background}")
print(f"Ratio : {round(ratio, 2)}:1")

# Check against WCAG 2.1 Standards
results = {
"AA (Normal Text)": ratio >= 4.5,
"AA (Large Text)": ratio >= 3.0,
"AAA (Normal Text)": ratio >= 7.0,
"AAA (Large Text)": ratio >= 4.5
}

for test, passed in results.items():
status = "✅ PASS" if passed else "❌ FAIL"
print(f"{test.ljust(18)}: {status}")

# --- Test It ---
check_contrast("white", "tomato")
print("\n")
check_contrast("#2c3e50", "#ecf0f1")


Understanding the WCAG Grades

The WCAG uses a grading system (AA and AAA) to define how accessible a design is.

LevelRatioTargetUse Case
AA (Large)3:1Text > 18ptThe bare minimum for bold headers.
AA (Normal)4.5:1Body TextThe standard for most professional websites.
AAA (Large)4.5:1Text > 18ptHigh accessibility for headlines.
AAA (Normal)7:1Body TextThe "Gold Standard" for maximum readability.

Note: If you are building a Dark Mode for your app, aim for at least AA (Normal). While absolute black and white ($21:1$) is safe, it can actually cause eye strain (halation) for some users. A dark grey like #121212 with off-white text is often more comfortable.


Sources & Technical Refs

More on python