The Accesible Perceptual Contrast Algorithm
For years, we´ve been relying on the WCAG Color contrast standard to guide us. Nowadays you can follow the Accessible Perceptual Contrast Algorithm (APCA).
Current WCAG contrast is calculated as the luminance of the foreground color divided by the luminance of the background.
(L_lighter + 0.05) / (L_darker + 0.05)
But this calculation has it's limitations because, our perception of how light or dark a color appears doesn’t match perfectly with its actual luminance value. If a color’s luminance is decreased by a certain percentage, we don’t necessarily see it as that exact percentage darker. How we perceive the change in luminance depends on several factors:
- The size and thickness of the colores element play a role. For example, a thin, small font or small symbols will appear differently compared to a big, bold headline font.
- The contrast between the color and its background matters. A darker color on a brighter background will be perceived differently than the same color on a darker background.
- Screen quality can affect color perception.
- The hue of both the element and the background influences how we see contrast.
- Individual vision impairments can alter color perception.
- The lightness of your environment (e.g., a well-lit room vs. a dark room) affects how colors appear.
- The padding between your colored element and the edges of the background can change perception.
So what APCA actually computes
- Take text color and background color separately. This is the conceptual break. APCA needs to know which is which. There is no "the two colors", there is foreground and background.
- Linearize each channel with a simple power curve (~2.4 exponent), then weight to luminance using the sRGB coefficients (R 0.2126, G 0.7152, B 0.0722). Green dominates and blue contributes almost nothing to perceived lightness.
- Soft-clamp the black level. Instead of a flat constant, APCA models screen flare and ambient reflection with a curve that only bends the very dark values. This is what fixes the dark-end collapse.
- Apply different exponents depending on polarity. Dark-text-on-light gets one pair of power curves, light-text-on-dark gets another. This is where the perceptual asymmetry lives. It's the whole point, and it's why APCA's output is signed.
- Output an Lc value ("lightness contrast"), roughly –108 to +106. Positive = dark text on light background. Negative = light text on dark. You normally work with the absolute value, but the sign tells you which curve was used.
Tools
https://apcacontrast.com for the calculator and font table, Polypane if you want it in a browser devtools context. There's also Bridge-PCA — Somers' variant that outputs APCA-quality judgments while staying compatible with WCAG 2 thresholds, which is a reasonable bridge if you need one number to hand to a client.