Home / Blog / Color & design

Color & design

Calculate and Check an sRGB Color Contrast Ratio

Convert sRGB channels to relative luminance, calculate the contrast ratio, verify boundary colors, and avoid rounding a failing result into a pass.

Start with two final opaque sRGB colors

A WCAG contrast ratio compares the relative luminance of two colors. It does not measure the numerical distance between their hexadecimal codes or simply subtract their RGB channels. Use the final opaque foreground and background colors that appear together. The method below takes ordinary six-digit sRGB hex values; transparency and a changing image background must be resolved before they can be represented by one fixed pair.

Set #000000 and #ffffff in the contrast checker. The result should be 21:1, the maximum for this calculation. Setting both controls to #ffffff gives 1:1, indicating no luminance contrast. These two simple fixtures help distinguish a ratio implementation from a generic color-difference score.

Convert the encoded channels into linear-light values

Split #RRGGBB into red, green and blue integers, then divide each by 255. For a normalized channel c at or below 0.04045, its linear value is c/12.92. Otherwise use ((c+0.055)/1.055)2.4. This transfer function matters: the familiar stored sRGB channel numbers are not already proportional to light output.

Combine the linear channels as L=0.2126R+0.7152G+0.0722B. Green contributes more strongly than blue in this relative-luminance expression. Two colors can therefore have similar sums of their stored RGB integers but quite different luminance. A hue wheel or saturation slider cannot replace this calculation when evaluating the specified ratio.

Apply the luminance formula without rounding early

Let Llight be the larger luminance and Ldark the smaller. The contrast ratio is (Llight+0.05)/(Ldark+0.05). Reversing foreground and background leaves this mathematical ratio unchanged, although their roles still matter when reviewing the actual component. Preserve full numerical precision until the final display and threshold comparison.

function luminance(hex) {
  if (!/^#[0-9a-f]{6}$/i.test(hex)) throw new Error("Use #RRGGBB");
  const channels = [1, 3, 5].map(i => parseInt(hex.slice(i, i + 2), 16) / 255);
  const [r, g, b] = channels.map(c =>
    c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
function contrast(a, b) {
  const x = luminance(a), y = luminance(b);
  return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05);
}
console.log(contrast("#000000", "#ffffff")); // 21
console.log(contrast("#ffffff", "#ffffff")); // 1

Test a pair near the normal-text threshold

The pair #777777 and white has a ratio around 4.47809:1. It does not meet a 4.5:1 requirement, even if a one-decimal display would round it to 4.5. Changing the gray to #767676 produces approximately 4.54222:1, which does meet that threshold. The local checker displays two decimal places but uses the unrounded result for its pass/fail decisions.

Do not copy a rounded label back into a rule such as “displayed ratio greater than or equal to 4.5.” Keep the original computed value for evaluation. A design token exactly on a boundary is also brittle: a later color adjustment or compositing change can invalidate it. A comfortably passing pair is easier to maintain than one selected solely to satisfy the last displayed digit.

Apply the ratio to the appropriate requirement

A number is not a complete verdict until the applicable criterion is identified. WCAG AA ordinary text uses 4.5:1, and qualifying large text uses 3:1. Enhanced AAA text uses higher thresholds. Non-text controls and meaningful graphical details have a separate criterion and context. The checker’s four text indicators do not inspect the content or determine which exceptions apply.

The example function deliberately does not parse shorthand hex, named colors, alpha, Display P3 or arbitrary CSS color syntax. Convert or resolve those inputs with an appropriate color-management workflow before applying this sRGB-only routine. Its explicit validation is preferable to silently interpreting an unsupported format as another color.

Compare the numerical result with the rendered interface

Use the WCAG relative-luminance definition and contrast-ratio definition to identify the method being implemented. A different perceptual contrast model should be named and interpreted under its own rules rather than compared directly with these numerical thresholds.

Finally inspect the actual typography, state and background. Antialiasing can make thin strokes appear lighter, but sampling a softened edge pixel is not the same as reading the authored text color. The ratio establishes one measurable property of a pair; it does not by itself establish readability in every presentation or conformance of a whole page.