Accessibility guide

Color Contrast Basics for UI Reviews

Extracting or converting a color is not the same as approving it for UI use. Contrast checks are what turn a visually interesting palette into something more reliable for text, controls, and layered surfaces.

Last Reviewed

2026-10-01

How This Was Tested

The numeric examples were checked with the HexAnyTool contrast calculator and with direct luminance math using the WCAG contrast formula.

Primary References

WCAG contrast guidance, sRGB linearization rules, and standard relative luminance weights.

Why contrast matters early

Contrast problems are easier to fix before a color choice spreads across multiple components. A button style, chart accent, or hero overlay may look attractive in isolation, but once text and interaction states are layered on top, the same color can become difficult to read.

Running a basic contrast review early saves rework. It helps you decide whether a color should be promoted to a text role, kept as decoration, or adjusted into a safer variation.

What a contrast ratio measures

A contrast ratio compares the relative luminance of two colors. The scale runs from `1:1` for identical colors to `21:1` for black against white. It measures luminance difference, not whether two hue names sound different, so two visibly distinct saturated colors can still have weak text contrast.

Under WCAG guidance, normal-sized text generally needs at least `4.5:1`. Large text generally needs at least `3:1`. A ratio of `7:1` is the enhanced target for normal text. Large text means at least 18 point regular or 14 point bold, which is approximately 24 CSS pixels regular or 18.66 CSS pixels bold under the usual 96 CSS pixels per inch conversion.

4.5:1

Minimum commonly used for normal text.

3:1

Minimum commonly used for large text and many UI boundaries.

7:1

Enhanced target for normal text.

How the ratio is calculated

Each sRGB channel is first normalized from `0–255` to `0–1`, then linearized because encoded sRGB values are not proportional to perceived light. Relative luminance is calculated with the weighted formula `0.2126R + 0.7152G + 0.0722B`. Green contributes most because human vision is more sensitive to it.

Once both luminance values are known, the ratio is `(lighter + 0.05) ÷ (darker + 0.05)`. Pure black against pure white produces the maximum `21:1`.

function linearize(channel) {
  const value = channel / 255;
  return value <= 0.04045
    ? value / 12.92
    : ((value + 0.055) / 1.055) ** 2.4;
}

function luminance(red, green, blue) {
  return (
    0.2126 * linearize(red) +
    0.7152 * linearize(green) +
    0.0722 * linearize(blue)
  );
}

function contrastRatio(first, second) {
  const lighter = Math.max(first, second);
  const darker = Math.min(first, second);
  return (lighter + 0.05) / (darker + 0.05);
}

A lightweight review sequence

  1. 1. Decide the intended role: text, background, border, or accent.
  2. 2. Compare the foreground and background pair you actually plan to ship.
  3. 3. Review hover, pressed, or overlay states separately if transparency is involved.
  4. 4. Keep only the colors that remain readable under real UI conditions.

Worked review: gray text on white

`#767676` on `#FFFFFF` has a contrast ratio of approximately `4.54:1`, narrowly clearing the common `4.5:1` threshold for normal text. A small change to `#777777` lowers the ratio to approximately `4.48:1`, just below that threshold.

This example shows why visual judgment alone is unreliable near a boundary. Two adjacent gray values look nearly identical, but one can pass a target while the other misses it.

Verified pairings you can reproduce

Foreground Background Ratio Result
#111827 #FFFFFF 17.74:1 Passes AA and AAA for normal text.
#767676 #FFFFFF 4.54:1 Barely passes AA for normal text.
rgba(37, 99, 235, 0.2) #FFFFFF 1.33:1 after compositing Not suitable for text without adjustment.

Common traps

One trap is evaluating a color on the wrong background. A pale accent may look fine on a dark hero image and fail completely on a white card. Another trap is forgetting transparent overlays. A foreground color that passes on a solid surface may behave differently once a semi-transparent tint sits underneath it.

Teams also sometimes over-promote extracted colors. Not every dominant image color should become text or interface chrome. Some values are best kept for illustration support, chart highlights, or limited decorative moments.

Transparency must be composited first

Do not calculate contrast directly from an RGBA source value. Blend it with the actual background first, then calculate the ratio from the resulting opaque color.

Contrast is not the only signal

Error, success, and selection states should not rely on color alone. Pair color with text, icons, borders, or other visible cues so meaning survives color-vision differences.

How the tools help

The image extractor is useful for discovering candidates, and the converter is useful for expressing the approved colors in the final CSS format your workflow expects. The contrast decision sits between those two steps and should remain explicit in your review process.

Review notes worth documenting

  • Write down which background was used for the final contrast decision.
  • Record whether transparency was flattened before the result was approved.
  • Keep borderline pass values under extra review because tiny adjustments can change the outcome.
Read the palette workflow guide Convert approved colors Back to guides