Skip to main content
← BlogAccessibility19 June 202611 min read

WCAG Colour Contrast: What It Covers and Where Teams Get It Wrong

Colour contrast is the most commonly detected WCAG failure on the web. Most teams know to check text against its background, but similar contrast requirements apply across multiple WCAG criteria covering button outlines, form field borders, icons, and focus rings. Here is what the standard actually covers and how to check the full picture.

Why it stays the most common failure

According to the WebAIM Million report, low contrast text was found on 83.9% of the top one million home pages in 2026, and it has consistently topped the list of most common failures across recent annual editions of the report.

The persistence is partly a design tool problem. Light grey text looks refined on a calibrated monitor in a well-lit room. The same text becomes unreadable on a mobile screen in sunlight, or for anyone with reduced vision, cataracts, or colour blindness. The failure is rarely intentional. It is invisible until you actually measure the ratio.

The other reason contrast keeps failing is scope. Teams often fix text contrast and miss the same problem on button outlines, form field borders, icons, and focus indicators. WCAG has three separate criteria and each covers different things.

Three requirements, not one

Contrast requirements in WCAG 2.2 span three different success criteria, each with its own threshold, scope, and conformance level.

Covers
Text and images of text
Threshold
4.5:1 for normal text, 3:1 for large text
Note
The most widely known criterion. Covers body copy, headings, labels, placeholder text, and text inside buttons.
Covers
UI components and informational graphics
Threshold
3:1 against adjacent colours
Note
Added in WCAG 2.1. Covers button outlines, input field borders, checkboxes, and icons that convey meaning on their own.
Covers
Keyboard focus indicators
Threshold
3:1 contrast plus minimum size/area requirements — see the understanding document for exact measurement rules
Note
New in WCAG 2.2. Requires focus indicators to be visible with a minimum contrast ratio and a minimum enclosed area. Level AAA; many teams adopt it as a practical target.

Text contrast in practice (WCAG 1.4.3)

The 4.5:1 threshold is what most teams are familiar with, but it is easy to fail without realising. The three shades below illustrate a common progression: the first is a grey that many design systems use for secondary text, the second is the minimum value that clears the AA bar on white, and the third passes AAA.

Left: #9ca3af (gray-400). A common "muted text" choice that fails every threshold. Middle: #6b7280 (gray-500). Just above the AA line for normal text. Right: #595959. Passes AAA.

styles.css
Before
/* Fails AA: 2.5:1 on white — too light for body copy */
.secondary {
  color: #9ca3af; /* gray-400 */
}

/* Fails AA: 3.5:1 on white — misses the 4.5:1 threshold */
.body {
  color: #888888;
}
styles.css
After
/* Passes AA: 4.7:1 on white */
.secondary {
  color: #6b7280; /* gray-500 — minimum safe muted text on white */
}

/* Passes AAA: 7.0:1 on white */
.body {
  color: #595959;
}

What counts as large text?

The WCAG 1.4.3 understanding document defines large text as at least 18 pt (24 px) at normal weight, or at least 14 pt (approximately 18.7 px) in bold. A heading at 20 px regular weight does not qualify, so the 4.5:1 threshold still applies. When in doubt, apply 4.5:1 across the board and use the 3:1 concession only for text that is visibly and clearly large.

Which sizes qualify as large text?
Sample
12 px regularNormal text — 4.5:1 min
Sample
16 px regularNormal text — 4.5:1 min
Sample
20 px regularNormal text — 4.5:1 min
Sample
18.67 px bold (14 pt bold)Large text — 3:1 min
Sample
24 px regular (18 pt)Large text — 3:1 min

Placeholders are not exempt

Placeholder text inside form fields must meet the same 4.5:1 threshold as any other visible text under 1.4.3. The light grey default placeholder is one of the most common failures in this category. Note that fixing contrast on a placeholder does not make placeholder-only labelling acceptable: inputs still need a persistent, associated label to satisfy WCAG 1.3.1.

Non-text contrast: borders, icons, and UI components (WCAG 1.4.11)

WCAG 1.4.11 requires a 3:1 contrast ratio for the visual boundaries of UI components against adjacent colours. For a form field, the border must be 3:1 against the background it sits on. For an icon-only button, the icon itself must be 3:1 against its background.

The most common failure is an input with a light grey border on a white page. A border such as gray-300 (#d1d5db) sits at roughly 1.5:1 against white, well below the threshold.

Before

border #d1d5db — 1.5:1 on white

After

border #6b7280 — 4.7:1 on white

styles.css
Before
/* Input border — fails 1.4.11: 1.5:1 against white */
input {
  border: 1px solid #d1d5db; /* gray-300 */
}

/* Icon in icon-only button — fails 1.4.11: 2.5:1 against white */
.icon-btn svg {
  color: #9ca3af; /* gray-400 */
}
styles.css
After
/* Input border — passes 1.4.11: 4.7:1 against white */
input {
  border: 1px solid #6b7280; /* gray-500 */
}

/* Icon in icon-only button — passes 1.4.11: 4.7:1 against white */
.icon-btn svg {
  color: #6b7280; /* gray-500 */
}

What is exempt from 1.4.11?

Decorative graphics and inactive UI components are exempt. A full-width hero image is not a UI component. However, any graphic or icon that conveys information on its own (a chart, a warning icon, a status indicator) must meet 3:1 against its background. The WCAG 1.4.11 understanding document has a full list of what is and is not in scope.

Hover and focus states

Hover states

WCAG does not have a dedicated hover contrast criterion, but the existing ones still apply to every state. When a hover interaction changes a colour, the new combination must still meet 1.4.3 for text and 1.4.11 for non-text elements.

A common mistake is a blue button with white text that passes in its default shade and then lightens on hover to a shade where it no longer does. The button was already borderline, and the hover made it worse.

Failing pattern

Default

Save
3.7:1Fail

Hover

Save
2.5:1Fail

Focus

Save
3.7:1Fail
ring: 2.0:1Fail

Corrected pattern

Default

Save
6.7:1Pass

Hover

Save
5.2:1Pass

Focus

Save
6.7:1Pass
ring: 6.7:1Pass

The ratio shown in each cell is for the button text against the button background. The focus cell includes a ring ratio as an illustration. WCAG 2.4.13 evaluates contrast between the focused and unfocused appearance of the indicator, along with area requirements, so a single ring-to-background ratio does not capture the full criterion.

styles.css
Before
/* Default: white on blue-500 — fails AA: 3.7:1 */
.btn {
  background: #3b82f6;
  color: #ffffff;
}

/* Hover lightens it further — 2.5:1, fails even more */
.btn:hover {
  background: #60a5fa;
  color: #ffffff;
}
styles.css
After
/* Default: white on blue-700 — passes AA: 6.7:1 */
.btn {
  background: #1d4ed8;
  color: #ffffff;
}

/* Hover: white on blue-600 — passes AA: 5.2:1 */
.btn:hover {
  background: #2563eb;
  color: #ffffff;
}

/* Focus: white ring on dark blue — 6.7:1 contrast */
.btn:focus-visible {
  outline: 3px solid #ffffff;
  outline-offset: 3px;
}

Focus indicators (WCAG 2.4.13)

WCAG 2.2 added a dedicated criterion for focus indicator appearance. At a high level, 2.4.13 requires that the focus indicator be visible, meet a minimum contrast ratio of 3:1 between the focused and unfocused appearance, and enclose a minimum area around the component. The exact measurement rules are specific to the criterion, so refer to the WCAG 2.4.13 understanding document for the full detail.

The most common failure is outline: none with no replacement, which removes visibility entirely. The second most common is replacing the default browser ring with something too thin or too low contrast to notice.

outline: none

No visible focus indicator

outline: 3px solid #2563eb

Clear keyboard focus, 5.2:1 ring contrast

styles.css
Before
/* Removes the focus ring entirely — fails 2.4.7 and 2.4.13 */
:focus {
  outline: none;
}

/* Visible but too low contrast: 2.0:1 against white — fails 2.4.13 */
:focus-visible {
  outline: 2px solid #93c5fd; /* blue-300 */
}
styles.css
After
/* Passes 2.4.13: high contrast, minimum 3 px wide, offset so it clears the component */
:focus-visible {
  outline: 3px solid #2563eb;  /* blue-600: 5.2:1 against white */
  outline-offset: 2px;
}

/* On dark backgrounds, switch to a light ring */
.dark-bg :focus-visible {
  outline-color: #ffffff; /* high contrast on dark */
}

Use :focus-visible, not :focus

The :focus-visible pseudo-class only shows the focus indicator when the browser determines keyboard navigation is active. Mouse users do not see an outline on click; keyboard and assistive technology users always do. It is supported in all current major browsers; Internet Explorer does not support it.

Checking your full colour palette

Checking one pair at a time works for a specific fix, but it does not tell you which combinations in your design system are safe to use together. A grid view is more practical for that: paste in every foreground and background token and see every combination at once.

The example below shows a simplified 3-by-3 grid. A real palette might have a dozen or more tokens, and many of the combinations will fail. The grid makes it easy to define which pairs are approved for use before they reach production.

Contrast ratio matrix: foreground colours vs background colours
Fg/Bg
#ffffff
#6b7280
#1e293b
#1e293b
Aa
14.6:1
Aa
3.0:1
Aa
1.2:1
#6b7280
Aa
4.7:1
Aa
1.2:1
Aa
4.7:1
#ffffff
Aa
1.0:1
Aa
4.7:1
Aa
14.6:1
Passes AALarge text onlyFails

Three tones, nine combinations. Dark on mid-grey (3.0:1) only passes for large text. White on white (1:1) and dark on dark are both unusable, as expected.

Paste in your entire colour palette and get an interactive grid showing the contrast ratio for every foreground and background combination. Filter by WCAG level to see at a glance which pairs are safe to use. Free, no account needed, and particularly useful during design system setup.

Enter a foreground and background colour and get an instant pass or fail against AA and AAA. Includes an interactive slider to adjust the colour until it passes. Good for checking individual pairs during development or code review.

Shows how a colour combination looks to people with different types of colour vision deficiency, low vision, and blindness. Useful for demonstrating the impact of contrast failures to a design team.

Figma users

Various Figma plugins provide contrast checking within the design file, including 1.4.11 checks for UI component boundaries. Running checks at design stage catches failures before they reach development.

Fix it at the token level

Contrast failures found across a site usually trace back to a small set of colour values, defined once and reused everywhere. Fixing the value at source is faster and more reliable than hunting it down page by page.

CSS custom properties make this straightforward. Documenting the ratio alongside each token means any future change can be evaluated before it ships, and the intent is clear to anyone reading the file.

tokens.css
Design tokens
/* Define tested colour pairs at token level — fix once, applies everywhere */
:root {
  /* Text colours, all tested on white (#fff) */
  --color-text-primary:   #1e293b;  /* 14.6:1 — AAA */
  --color-text-body:      #334155;  /*  9.8:1 — AAA */
  --color-text-secondary: #475569;  /*  7.6:1 — AAA */
  --color-text-muted:     #6b7280;  /*  4.7:1 — AA  */

  /* UI components — 3:1 minimum required by 1.4.11 */
  --color-border-input:   #6b7280;  /*  4.7:1 against white */
  --color-icon-default:   #6b7280;  /*  4.7:1 against white */
  --color-focus-ring:     #2563eb;  /*  5.2:1 against white */
}

With tokens in place, a contrast failure found in an audit means updating one value, not searching through a codebase. If a colour ever changes, the comment tells you exactly what ratio to re-verify.

What Destiny QA detects automatically

Destiny QA checks every crawled page for contrast failures against WCAG 1.4.3 and flags each instance with the computed ratio, the element, and the criterion being violated. Each finding includes a screenshot that shows the failing element highlighted in context, so you can see at a glance exactly what is affected and where it sits on the page.

Findings are grouped by page so you can see where failures are densest and which pages are pulling the overall score down. Because the check runs across the whole site on every audit, a problematic colour value baked into a shared component appears on every page that uses it. That is usually the fastest way to trace a failure back to the design system value that is responsible.

acme-example.com/about
ACMEAboutServicesContact

About our team

2.5:1 ratio — fails WCAG 1.4.3 (required 4.5:1)

We build software that makes the world easier to navigate.

Our mission is to remove friction from every step of the journey.

Elementp.intro-textRatio2.5:1 (required 4.5:1)CriterionWCAG 1.4.3 Contrast (Minimum)

Screenshot captured automatically on every audit. Highlights show the exact failing element in context.

Find contrast failures on your site

Run a free audit on your site

Destiny QA checks every page for contrast failures, missing labels, and the other common WCAG issues, then shows you exactly where to start.

Start free

Cookie Consent Preferences

We use cookies to enhance your browsing experience, serve personalized content, and analyze our traffic. By clicking "Accept All", you consent to our use of all cookies. You can manage your preferences or read our Cookie Policy for details.