Accessibility

Blue Button Contrast: Choose Readable Text with WCAG Examples

Calculate contrast for common blue buttons, see why white text can fail, and compare foreground and background changes against WCAG AA text requirements.

Start with the actual text requirement

WCAG 2.2 Success Criterion 1.4.3 requires at least 4.5:1 contrast for normal text at level AA, and 3:1 for large text. Large text means at least 18 pt, or 14 pt when bold—approximately 24 CSS px or 18.67 CSS px respectively. A typical 16 px button label needs 4.5:1, even if it is bold.

Measure the label against the background immediately behind it. A blue that looks saturated is not automatically dark enough for white text. A design-system token name or a successful preview on one display is not a substitute for calculating the pair.

Compare white and dark text on real blues

The following ratios use opaque sRGB colors and the WCAG relative-luminance formula. Values are displayed to two decimal places, while the pass decision uses the unrounded result. WCAG thresholds must not be rounded up to manufacture a pass.

On #3B82F6, white text fails AA for normal text, while #111827 passes. Darkening the background to #2563EB lets white text pass. Ant Design's default #1677FF illustrates why both options should be tested: neither white nor #111827 reaches 4.5:1 in this particular pair of comparisons.

WCAG AA results for normal text on opaque blue backgrounds
BackgroundTextContrastAA normal text
#3B82F6#FFFFFF3.68:1Fail
#3B82F6#1118274.82:1Pass
#2563EB#FFFFFF5.17:1Pass
#1677FF#FFFFFF4.10:1Fail
#1677FF#1118274.32:1Fail
#3F51B5#FFFFFF6.87:1Pass

Interpret a ratio without overstating the result

A contrast ratio belongs to a pair of colors, not to a blue in isolation. The two failed #1677FF examples do not prove that every dark foreground will fail or that the library is inaccessible in every configuration. A different foreground, a different generated state, or a customized theme must be measured separately. Record the exact CSS values used in the component before drawing a conclusion about it.

A measured pass is also specific to the requirement being checked. Normal text at level AAA needs 7:1, while large text at that level needs 4.5:1. A 5.17:1 pair passes AA for normal text but does not meet AAA for normal text. When presenting results to a team, state the conformance level, text size, and color pair together rather than labeling the color simply accessible.

Leave practical room above a minimum where the design permits it. W3C notes that thin or unusual fonts can look fainter after anti-aliasing even when their CSS color pair passes. Use the specified foreground and background for the formal calculation, then inspect the rendered label for readability; sampling a softened edge pixel is not the basis of the text-contrast calculation.

Calculate relative luminance correctly

For each sRGB channel, divide its 0–255 value by 255. If the normalized value is at most 0.04045, divide it by 12.92; otherwise calculate ((value + 0.055) / 1.055) to the power of 2.4. Relative luminance is 0.2126R + 0.7152G + 0.0722B using those linearized channels.

Contrast is (lighter luminance + 0.05) / (darker luminance + 0.05). This example deliberately accepts only opaque six-digit HEX colors. Transparent layers and gradients need the actual rendered color at the text position before applying the formula.

JavaScript
function luminance(hex) {
  if (!/^#[0-9a-f]{6}$/i.test(hex)) {
    throw new Error('Expected an opaque #RRGGBB color');
  }
  const channels = [1, 3, 5].map((start) => {
    const value = parseInt(hex.slice(start, start + 2), 16) / 255;
    return value <= 0.04045
      ? value / 12.92
      : ((value + 0.055) / 1.055) ** 2.4;
  });
  return channels[0] * 0.2126
    + channels[1] * 0.7152
    + channels[2] * 0.0722;
}

function contrastRatio(foreground, background) {
  const values = [luminance(foreground), luminance(background)];
  return (Math.max(...values) + 0.05) / (Math.min(...values) + 0.05);
}

const ratio = contrastRatio('#FFFFFF', '#2563EB');
console.log(ratio.toFixed(2)); // 5.17
console.log(ratio >= 4.5);     // true

Fix a failing button through its semantic role

Consider a 16 px white label on #3B82F6. Start by confirming that the label is normal text and the background is fully opaque. Its ratio is approximately 3.68:1, so adding font weight does not make it pass. If white text is an intentional part of the primary-button design, changing the action background to #2563EB gives approximately 5.17:1 in the default state.

If the original background must stay, changing the label to #111827 gives approximately 4.82:1 for this particular blue. Choose between these options using the component hierarchy and the brand rules, then change the semantic token used by the button. Replacing the global brand color everywhere can unnecessarily alter illustrations, links, selected surfaces, and other components that have different contrast relationships.

After either adjustment, inspect the hover and pressed styles independently. A passing default button can still fail when a hover rule makes the fill lighter or applies opacity to the entire element. Opacity affects the visible text as well as the fill, so calculate the final composited pair. For a gradient or image, inspect the region behind the label and address its lowest-contrast area instead of testing an average background color.

Validate every state, not just the default button

Choose a passing text and background pair, then repeat the check for hover, pressed, selected, and focus states. If a gradient sits behind the label, inspect the lowest-contrast area that the text covers. For transparent backgrounds, calculate the composited color on the actual surface.

Text contrast is only one part of accessibility. WCAG 1.4.11 addresses certain non-text UI and graphical contrast at 3:1, and keyboard focus must remain visible. A label passing 4.5:1 does not by itself prove that the component or page meets every WCAG requirement.

Evaluate the visual information required to identify the control or its state against adjacent colors. This does not mean every decorative border needs 3:1, or that the hover fill must contrast 3:1 with the default fill when they are not shown next to one another. A custom focus indicator needs a deliberate review of the colors it touches, as well as a keyboard check that the indicator is actually visible.

Inactive controls have exceptions under the relevant contrast criteria. Apply those exceptions to genuinely inactive controls rather than using a disabled-looking style to excuse low-contrast active text. Secondary actions, helper text, and placeholders are not automatically exempt because they are visually less prominent.

Use the analyzer to inspect the text options for your chosen background. If your design needs a specific foreground, calculate that exact pair rather than assuming the tool's suggested light or dark text matches your final CSS.

Make the check reproducible for the next change

For each button variant, record the theme, state, foreground, resolved background, font size, font weight, unrounded ratio, and applicable threshold. Include the underlying surface when transparency is involved. Store a displayed ratio for communication, but keep sufficient calculation precision to avoid accepting a value such as 4.499:1 as 4.5:1.

Automated checks are useful for finding simple color-pair failures, but they may need manual input for gradients, images, overlays, and changing states. Inspect the computed styles in the browser, exercise the button with a keyboard, and verify the actual hover and pressed rendering. A screenshot explains context, while the recorded colors and calculation allow another reviewer to reproduce the result.

Repeat the checks when a theme seed, component override, font, or surface changes. Treat the result as evidence for a particular component configuration. Keep the scope of the claim clear: passing text contrast is one verified requirement, while a complete accessibility assessment also covers naming, keyboard operation, focus, and other applicable criteria.

Sources & further reading

← Back to all articles