Skip to content
Tools Without AI

Prefer toolswithoutai.com in Google Search so our tools and guides show up more often. Google has to confirm it — we cannot set this for you.

Accessibility · · updated

The mathematics of WCAG color contrast formulas

WCAG 2.x contrast is a ratio of relative luminances, not a design opinion. Linearize each sRGB channel, dot with 0.2126, 0.7152, 0.0722, then divide (lighter + 0.05) by (darker + 0.05). A checker in the tab runs that function on two colors. No screenshot API is involved. Open the contrast checker and convert tokens in the color converter.

How is relative luminance calculated?

For each 8-bit channel, divide by 255. If c <= 0.04045, use c/12.92; else ((c+0.055)/1.055)^2.4. L = 0.2126R + 0.7152G + 0.0722B. These coefficients are Rec. 709 / sRGB. Skipping gamma is the most common false implementation and will pass pairs that fail in the spec.

HSL lightness is not L. Designers who “raise L in HSL until it looks fine” still need this formula. Convert in the color converter, then check. The shorter product view is WCAG ratios in the browser.

function lin(c) {
  const s = c / 255;
  return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
}
const L = 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);

How do AA and AAA map from the ratio?

ratio = (max(L1,L2)+0.05)/(min(L1,L2)+0.05). Compare to 4.5, 7, 3. Large-text exceptions are typography, not color. 1.4.11 covers icons and graphics. Placeholder text at 0.5 opacity is not the hex in the design token — composite first.

Alpha compositing before luminance

RGBA text over a background must be composited: out = src*a + dst*(1-a) per channel in sRGB (imperfect but what CSS does for simple cases), then luminance. Checking #333 at 0.5 opacity against white as if it were opaque #333 is wrong.

P3, Rec.2020, and CSS color-contrast()

Engine functions may target similar outcomes. For audits, implement WCAG 2 explicitly on sRGB. Palettes that only rotate hue can still fail; run palette generator output through the checker pair by pair.

CI and screenshots

Unit-test tokens with the same functions. Do not upload unreleased mock screenshots to a contrast SaaS. HEX values are not secret, but the habit of upload-for-everything is how EXIF and PDFs leak elsewhere. Keep contrast local.

Tools

Contrast checker for the ratio. Color converter for HEX/RGB/HSL. Palette generator for related hues you will still have to test.

Worked example: token pair that fails AA

Brand gray #767676 on white looks “fine” on a calibrated display and fails 4.5:1. Paste both into contrast checker. Convert the gray to HSL in color converter, lower lightness (or raise, depending), convert back, recheck. Do not trust HSL L as WCAG L. Details of HEX parsing are in color spaces.

Generate a complementary pair with palette generator. Every pair used for text still goes through the checker. Decorative pairs can fail AA; text pairs cannot.

Opacity: a 16px label at 0.54 on a photo is not the token you checked on white. Composite, then measure. If you cannot composite, do not claim AA. The product-oriented WCAG post is the shorter companion.

CI: export tokens as JSON, unit-test the formula in this article. No screenshot SaaS. HEX is small enough that privacy is not the point; habit is. The same upload reflex is how EXIF leaks.

Frequently asked questions

What ratio is WCAG AA for body text?

4.5:1 normal text, 3:1 large text (18pt or 14pt bold). AAA is 7:1 and 4.5:1. Non-text UI (1.4.11) is 3:1. The contrast checker maps these thresholds for you.

Does APCA replace this formula?

APCA is the WCAG 3 candidate. Until your legal or contractual bar is WCAG 2.2, ship the 2.x ratio and treat APCA as extra.

Can I use Display P3 coefficients with this formula?

The published WCAG 2 math assumes sRGB. Convert wide-gamut tokens down first. HEX parsing lives in the color space article.