Skip to course content
Free data visualization course

Data Visualization and Dashboard Storytelling

Unit 08.01: Checking contrast before anyone complains

Contrast is measurable, so it can be checked before anyone complains.

The 4.5:1 threshold

Relative luminance produces a contrast ratio for any pair of colours. The usual thresholds are 4.5:1 for body text and 3:1 for larger text and graphical elements.

The code computes the ratio for five common pairs.

def relative_luminance(rgb):
    def channel(c):
        c = c / 255
        return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
    r, g, b = (channel(v) for v in rgb)
    return 0.2126 * r + 0.7152 * g + 0.0722 * b


def contrast(a, b):
    la, lb = relative_luminance(a), relative_luminance(b)
    hi, lo = max(la, lb), min(la, lb)
    return (hi + 0.05) / (lo + 0.05)


PAIRS = [
    ("black on white",        (0, 0, 0),       (255, 255, 255)),
    ("mid grey on white",     (150, 150, 150), (255, 255, 255)),
    ("light grey on white",   (200, 200, 200), (255, 255, 255)),
    ("blue on white",         (31, 119, 180),  (255, 255, 255)),
    ("yellow on white",       (255, 221, 51),  (255, 255, 255)),
]
print(f"{'pair':24} {'ratio':>7}  meets 4.5:1 for text?")
for name, fg, bg in PAIRS:
    ratio = contrast(fg, bg)
    print(f"{name:24} {ratio:>6.1f}:1  {'yes' if ratio >= 4.5 else 'NO'}")

# 4.5:1 is the usual threshold for body text and 3:1 for large text and graph
# elements. Yellow on white fails badly and is chosen constantly, because on a
# bright screen in a dark room it looks fine to the person choosing it.

Yellow on white fails badly and is chosen constantly, because on a bright screen in a dim room it looks perfectly readable to the person choosing it. Light grey on white fails for the same reason.

This is one of the few things in visual design with a number attached. Computing it takes a moment and removes the argument entirely.

The mistake this prevents

The mistake is judging contrast on your own monitor. Projectors wash out, printed handouts lose saturation, and a phone in daylight is a different medium again. The ratio is invariant; your impression is not.

Takeaway

Compute the contrast ratio rather than judging it. 4.5:1 for text, 3:1 for graphical elements, and your monitor is not the medium it will be read on.