Follow Us

How to Check Color Contrast for Accessibility: WCAG Ratios, Tools, and Common Fixes

How to Check Color Contrast for Accessibility: WCAG Ratios, Tools, and Common Fixes

Color contrast is the single most common accessibility failure on the web. Automated scans of the top million home pages routinely find low-contrast text on more than three quarters of them, and it is also one of the easiest problems to fix once you know what to measure.

This guide walks through how to check color contrast for accessibility from first principles: how the ratio is actually calculated, which threshold applies to which element, how to audit an existing page with free browser tools in about ten minutes, and how to fix the five failures that come up in nearly every design review.

What a contrast ratio actually measures

A contrast ratio is not a measure of how different two colors look. It is a measure of the difference in relative luminance, which is a weighted estimate of how much light a color emits on a scale from 0 (pure black) to 1 (pure white).

The formula defined in WCAG 2.x works like this:

  1. Take each of the R, G and B channels of a color and divide by 255 to get a value between 0 and 1.
  2. Linearize each channel. If the value is at or below 0.03928, divide it by 12.92. Otherwise apply ((value + 0.055) / 1.055) ^ 2.4.
  3. Combine the channels with human-eye weighting: L = 0.2126 R + 0.7152 G + 0.0722 B.
  4. Compare the two luminances: (L_lighter + 0.05) / (L_darker + 0.05).

The result ranges from 1:1 (identical colors) to 21:1 (pure black on pure white).

Why green matters more than blue

Notice the coefficients: green carries 71.5% of the weight, red 21.3%, blue only 7.2%. That is why pure blue text (#0000FF) on white scores a surprisingly strong 8.59:1 while pure yellow (#FFFF00) on white collapses to 1.07:1 and is essentially invisible. Two colors can look wildly different to a designer and still fail, because hue difference is not luminance difference.

A worked example you can verify

Take the classic “soft gray” body text, #777777 on a white background:

  • 119 / 255 = 0.4667
  • ((0.4667 + 0.055) / 1.055) ^ 2.4 = 0.1844
  • All three channels are equal, so L = 0.1844
  • (1.0 + 0.05) / (0.1844 + 0.05) = 4.48:1

That fails the 4.5:1 requirement by two hundredths of a point. Nudge it to #767676 and you get 4.54:1, which passes. This is why eyeballing is never enough: the pass/fail line sits inside a range of grays no human can reliably distinguish.

color contrast checker

Which threshold applies to what

Most teams memorize “4.5:1” and stop there. In practice WCAG 2.2 defines three separate contrast requirements that apply to different things, and knowing which is which saves a lot of pointless redesign work.

Success criterion Level Minimum ratio Applies to
1.4.3 Contrast (Minimum) AA 4.5:1 Normal text and images of text
1.4.3 Contrast (Minimum) AA 3:1 Large text: 24px (18pt) regular, or 18.66px (14pt) bold and above
1.4.11 Non-text Contrast AA 3:1 UI component boundaries and states, plus graphics needed to understand content
1.4.6 Contrast (Enhanced) AAA 7:1 / 4.5:1 Normal text / large text
2.4.11 Focus Not Obscured AA n/a (visibility) Focus indicator must remain at least partially visible

What the 3:1 non-text rule really covers

This is the criterion teams misapply most often. 1.4.11 asks for 3:1 against adjacent colors for:

  • Visual boundaries of interactive controls: the border of a text input, the edge of a checkbox, the outline of a ghost button, a toggle track.
  • State indicators: the checked state of a checkbox, the active tab underline, the thumb position of a switch.
  • Meaningful graphics: icon-only buttons, chart lines and segments, required-field asterisks, progress bars, map legends.

It does not cover decorative dividers, background patterns, shadows that carry no information, or the inside fill of a button whose boundary is already identifiable by its label.

The exemptions worth knowing

  • Disabled controls are explicitly exempt from both 1.4.3 and 1.4.11. A grayed-out button can technically be any contrast and still pass.
  • Logotypes are exempt. Brand wordmarks do not have to meet 4.5:1.
  • Pure decoration and text that is invisible to everyone is exempt.
  • Incidental text in a photo, such as a street sign in a stock image, is exempt.

Treat the disabled-state exemption as a legal floor, not a design target. More on that below.

color contrast checker

How to check color contrast for accessibility: a 10 minute page audit

Here is the workflow we use when auditing a live page. It uses only free tools and does not require installing anything beyond a browser extension or two.

Step 1: Run an automated sweep first

Start broad so you know where to look. In Chrome or Edge, open DevTools, go to the Lighthouse panel and run an Accessibility audit. The “Background and foreground colors do not have a sufficient contrast ratio” item will list every failing node with its computed ratio.

Alternatives that catch slightly different things:

  • WAVE (browser extension from WebAIM): click the Contrast tab in the sidebar to see every failure flagged in place, with live sliders to test a fix.
  • axe DevTools: strong on rendered state and shadow DOM, flags “incomplete” items where it cannot determine the background.
  • Chrome DevTools CSS Overview: generates a list of every contrast-failing text/background pair used across the page, grouped by color combination. This is the fastest way to find systemic palette problems rather than one-off components.

Step 2: Inspect individual elements with the DevTools color picker

Automated tools report, but the color picker lets you fix in place:

  1. Right click the text, choose Inspect.
  2. In the Styles pane, click the color swatch next to the color property.
  3. The picker shows the contrast ratio with a checkmark or warning icon.
  4. Expand the ratio to reveal contrast guide lines drawn across the color gradient. Anything above the line passes AA, anything above the second line passes AAA.
  5. Drag the handle onto the line to snap to the nearest passing color, then copy the hex.

Firefox offers the same idea from a different angle: open the Accessibility panel, choose Check for issues > Contrast, and it highlights failures directly in the viewport. Firefox also has a simulation mode for color vision deficiencies in the same panel. Much the same conclusion turns up on webaim.org.

Step 3: Handle the cases automation cannot see

Automated checkers read computed CSS. They are blind to anything rendered as pixels. For those cases, use an eyedropper-based checker:

  • WebAIM Contrast Checker: paste two hex values, or use the eyedropper to sample foreground and background from anywhere on screen. It reports AA and AAA results for normal text, large text and graphical objects at once.
  • TPGi Colour Contrast Analyser (CCA): a free desktop app for Windows and macOS. This is the right tool for PDFs, slide decks, email templates and images of text, which is exactly what the Section 508 guidance recommends for non-web documents.
  • Adobe Express and Coolors contrast checkers: useful at the palette stage, before a single line of CSS exists.

Items that always need manual sampling:

  • Text sitting on a photo, video or hero image
  • Text on a gradient (check the lightest and darkest points along the text)
  • Semi-transparent overlays, where the effective color is a blend
  • Text inside SVG or canvas
  • Anything rendered in an iframe from a third party, such as embedded forms or chat widgets

Step 4: Test every interactive state

This is the step most audits skip. A button can pass at rest and fail on hover. Walk through, for each component:

  • Default
  • Hover
  • Focus (keyboard focus ring against both the component and the page background)
  • Active / pressed
  • Selected / checked
  • Error and success states
  • Visited links

In DevTools you can force states: right click the element in the Elements panel and choose Force state, then re-sample.

Step 5: Test both themes and real conditions

If you ship a dark mode, audit it as a separate design. Light-on-dark behaves differently: pure white text on a pure black background scores 21:1 but causes halation for many readers with astigmatism. Off-white text (around #E6E6E6) on a dark gray (#121212) is usually more comfortable and still clears 15:1.

Also check under forced colors mode (Windows High Contrast). In DevTools, open the Rendering panel and emulate forced-colors: active to confirm nothing disappears.

Step 6: Record results so they stick

Log each failure with the selector, the two hex values, the measured ratio, the required ratio and the proposed replacement. Then push the fix up into design tokens rather than patching one component. If the same gray fails in eleven places, it is a token problem, not eleven bugs.

The failures designers hit most often, and how to fix them

1. Light gray placeholder and helper text

Browser default placeholder styling lands around #757575 to #A9A9A9 depending on the engine, and most design systems push it lighter for a “clean” look. At #AAAAAA on white you get 2.32:1, roughly half the required ratio.

The fix:

  • Set placeholder color explicitly to at least #767676 on white (4.54:1). Do not rely on the browser default.
  • Better: stop using placeholders to carry information. Move the label above the field as a persistent <label> and use the placeholder only for format examples, or drop it entirely.
  • For helper and caption text, use the same 4.5:1 floor. Small text is where contrast matters most, not least, so shrinking the size and lightening the color at the same time compounds the problem.
/* fails: 2.32:1 */
::placeholder { color: #aaa; }

/* passes: 4.54:1 */
::placeholder { color: #767676; opacity: 1; }

Note the opacity: 1. Firefox applies a default placeholder opacity that silently darkens or lightens your chosen value. Related reading: Color and Color Contrast for Accessibility.

2. White text on a brand color

This is the hardest political fight in accessibility work. Mid-tone brand blues, greens, oranges and reds almost never carry white text at 4.5:1. A typical example: white on #1E88E5 gives 3.13:1, which fails for body text and button labels.

Options, in order of preference:

  1. Darken the brand color for interactive surfaces only. Keep the original hue as the marketing/identity color and create a darker “action” variant for buttons and links. #1565C0 with white gives 5.07:1 and still reads as the same blue.
  2. Flip the polarity. Use the brand color as text on white, and reserve the solid fill for large headings or decorative blocks where the 3:1 large-text threshold applies.
  3. Use a dark text color on the brand fill. On a bright yellow or lime, near-black text at 12:1+ is far more readable than white at 1.5:1.
  4. Increase size and weight. If the text is genuinely 24px or 18.66px bold, the bar drops to 3:1. This rescues hero headings but not button labels or body copy.

Practical tip for the stakeholder conversation: build a two-column table showing the current ratio and the proposed ratio for each brand pairing, with the hex codes side by side. The visual difference between #1E88E5 and #1565C0 is almost nothing; the compliance difference is everything.

3. Disabled button and input states

Disabled controls are exempt from contrast requirements, which is why most design systems render them at 20% opacity and move on. The result is a control nobody can read, including the people who need to know why they cannot proceed.

The fix:

  • Aim for at least 3:1 on disabled label text even though it is not required. Something like #949494 on white (3.03:1) still reads as clearly inactive.
  • Never signal “disabled” with color alone. Combine reduced contrast with a different cursor, a removed shadow, and an explicit explanation in text.
  • Strongly consider not disabling the button at all. Keep the submit button enabled, let the user click, then surface inline validation errors. This is better for screen reader users (disabled buttons are skipped by some navigation modes) and removes the contrast question entirely.
  • If you must disable, use aria-disabled="true" with a visible explanation rather than the disabled attribute, so the control stays focusable and discoverable.

4. Ghost buttons, outlined inputs and thin borders

A 1px border at #E0E0E0 on white is 1.24:1. If that border is the only thing telling a user where the input field is, it fails 1.4.11.

The fix: take borders of interactive elements to at least 3:1 (#767676 gives you plenty of headroom at 4.54:1, #8F8F8F sits right around 3.2:1). If the design demands a soft border, add a second cue: a filled background tint, a visible persistent label, or an increased border width. Decorative dividers between content sections are exempt, so you can keep those light.

5. Focus indicators

Removing outline: none without a replacement is still common. A focus ring must reach 3:1 against the adjacent background, and under WCAG 2.2 it must not be hidden behind sticky headers or cookie banners.

A reliable pattern:

:focus-visible {
  outline: 3px solid #0B5FFF;
  outline-offset: 2px;
}

The offset creates a gap so the ring reads against both the component and the page. For rings that need to work on both light and dark surfaces, use a two-tone ring: a dark outline with a light inner shadow, or vice versa.

6. Color as the only indicator

Related but distinct: SC 1.4.1 (Use of Color). Even with perfect contrast, you fail if color is the only way to tell something apart.

  • Links inside paragraphs need an underline or another non-color cue, unless the link text hits 3:1 against the surrounding text and gains a cue on hover and focus.
  • Error states need an icon or text, not just a red border.
  • Charts need direct labels, patterns or varied line styles, not just a color legend.
  • Required fields need the word “required” or an asterisk with an explanation, not just a red label.
color contrast checker

Build contrast checks into the workflow, not the end of it

Auditing a finished page is remedial work. The cheaper path is to stop failing colors from entering the system:

  1. Define tokens with pass/fail documented. For each color token, record which background colors it is approved against and at which ratio. A token named text-secondary should carry a note: “4.6:1 on surface-default, do not use on surface-brand.”
  2. Add a contrast plugin in the design tool. Figma has free plugins (Stark, Contrast, Able) that check selections live, catching problems before handoff.
  3. Add linting to CI. Run axe-core or Pa11y against key routes in your pipeline and fail the build on new contrast violations. Catching one regression here costs minutes; catching it after launch costs a release cycle.
  4. Snapshot the states. Include hover, focus and disabled variants in your component library screenshots so reviewers can see them without hunting.
  5. Re-test after every theme change. A single change to a background token can invalidate dozens of previously passing pairings.

A note on where the standard is heading

WCAG 2.2 remains the operative standard for compliance today, and it is what EN 301 549 and the European Accessibility Act regime point to, as well as the baseline used in most US procurement and litigation. If you are testing right now, test against WCAG 2.2 Level AA.

In parallel, the W3C is working on WCAG 3.0, which explores a perception-based model (APCA) that accounts for font size, weight and polarity instead of a single luminance ratio. It is worth watching, but it is a draft with no conformance status. Do not design to it, and do not let a vendor tell you a design passes because it scores well under APCA while failing 1.4.3.

color contrast checker

Quick reference: ratios for common pairs on white

Foreground Ratio on #FFFFFF Normal text AA Large text / UI AA
#CCCCCC 1.61:1 Fail Fail
#AAAAAA 2.32:1 Fail Fail
#949494 3.03:1 Fail Pass
#777777 4.48:1 Fail (just) Pass
#767676 4.54:1 Pass Pass
#595959 7.00:1 Pass (AAA) Pass
#1E88E5 3.13:1 Fail Pass
#1565C0 5.07:1 Pass Pass

FAQ

What are the accessibility guidelines for color contrast?

WCAG 2.2 Level AA requires 4.5:1 for normal text, 3:1 for large text (24px regular or 18.66px bold and above), and 3:1 for user interface component boundaries, state indicators and meaningful graphics. Level AAA raises the text requirements to 7:1 and 4.5:1 respectively. Logos, disabled controls, purely decorative elements and incidental text in photographs are exempt.

How can I check if my color palette is accessible?

Build a matrix of every text color against every background color it could appear on, and run each pair through a contrast checker such as WebAIM, Adobe Express or Coolors. Document the result next to each token. Then run a real page through Chrome DevTools CSS Overview or WAVE to catch pairings the matrix did not anticipate, like text over images or third-party widgets.

How do I fix color contrast accessibility issues?

Adjust the lighter color toward the background’s opposite end of the luminance scale rather than changing hue. In practice that means darkening text on light backgrounds and lightening text on dark backgrounds. If brand constraints block that, increase font size and weight to qualify for the 3:1 large-text threshold, add a solid or scrim background behind text on imagery, or swap which color is foreground and which is background.

What colors provide the best contrast for accessibility?

Near-black text (#1A1A1A to #333333) on a slightly off-white background (#FAFAFA to #FFFFFF) is the most reliable combination, landing between 12:1 and 17:1 without the harshness of pure black on pure white. For dark themes, off-white text around #E6E6E6 on a #121212 surface works well. Avoid pure yellow, light cyan and mid-tone saturated colors as text, and avoid relying on red/green pairings, which many users with color vision deficiency cannot distinguish.

Do automated tools catch every contrast failure?

No. Automated checks typically catch somewhere between a third and a half of accessibility issues overall, and for contrast specifically they are blind to text on images, gradients, canvas, video overlays and states that only appear on interaction. Always pair an automated sweep with manual eyedropper sampling and a keyboard pass through every interactive state.

Does contrast apply to icons and charts?

Yes, when they carry meaning. An icon-only button, a chart line, a required-field marker or a status dot must reach 3:1 against its adjacent background under SC 1.4.11. Purely decorative icons that sit next to a visible text label are exempt, though keeping them legible is still good practice.

Is a disabled button required to meet contrast requirements?

Technically no, since WCAG exempts inactive controls. But an unreadable disabled state creates real usability failures, so target 3:1 anyway and always pair the visual change with a text explanation of why the control is unavailable.

Where to start tomorrow

Pick your three highest-traffic pages. Run Lighthouse and CSS Overview on each, sample any text that sits on an image, force hover and focus states on every button and input, and write down the failing hex pairs. You will almost certainly find that four or five tokens account for most of the violations. Fix those at the token level and a large share of your contrast debt disappears in one commit.

If you want a hand turning that audit into a prioritized remediation plan, the team at Purlize works with product and design teams to move accessibility checks upstream, into the design system and the build pipeline, so contrast stops being something you discover after launch.

Leave a Reply

Your email address will not be published. Required fields are marked *