Color contrast failures on e-commerce sites: what breaks and how to fix it
Color contrast is the most common accessibility failure we find when scanning e-commerce stores. On many sites, it accounts for more than half of all flagged issues. The failure pattern is almost always the same: a designer chose colors that look fine on a calibrated display in a well-lit studio, and those same colors fall below the minimum contrast ratio that WCAG requires.
This guide covers what the standard requires, where e-commerce stores most often fail, and the specific changes needed to fix each pattern.
What contrast ratio means
Contrast ratio is a measure of how much the relative luminance of two colors differs. Luminance is a mathematical value derived from a color's RGB components, adjusted for how human vision perceives brightness. The ratio is expressed as X:1, where the higher the number, the greater the difference between foreground and background.
A contrast ratio of 1:1 means the two colors are identical and text is invisible. A ratio of 21:1 means pure black on pure white, the highest possible value. WCAG sets minimum thresholds based on text size because smaller text requires higher contrast to remain readable.
What WCAG requires
| Text type | Minimum ratio (AA) | Enhanced ratio (AAA) |
|---|---|---|
| Normal text (below 18pt / 14pt bold) | 4.5:1 | 7:1 |
| Large text (18pt+ / 14pt+ bold) | 3:1 | 4.5:1 |
| UI components (borders of form inputs, button outlines) | 3:1 | Not defined |
| Decorative images and logotypes | No requirement | No requirement |
| Disabled controls | No requirement | No requirement |
The AA level is the standard most e-commerce stores should target. It is the level referenced in accessibility complaints and the DOJ guidance for Title II entities. The AAA level is aspirational and not required for general compliance purposes.
In CSS units, 18pt is approximately 24px, and 14pt bold is approximately 18.67px. A heading that is 24px or larger is considered large text and only needs to meet the 3:1 threshold. Body copy and most UI text needs 4.5:1.
Where e-commerce stores most often fail
1. Light gray price text
The most common single failure on product pages. A price displayed in a medium gray like #999999 on a white background has a contrast ratio of 2.85:1 -- well below the 4.5:1 required for normal text. This appears on product tiles, search results, and product detail pages across most themes.
Common failing combination and a passing alternative:
/* Fails: #999999 on #ffffff = 2.85:1 */
.product-price {
color: #999999;
}
/* Passes: #767676 on #ffffff = 4.54:1 (just over the threshold) */
.product-price {
color: #767676;
}
/* Stronger: #595959 on #ffffff = 7.0:1 */
.product-price {
color: #595959;
}
The value #767676 is the approximate threshold for normal-weight body text on a white background. Any gray lighter than this will fail. If your design requires very light text, use a colored background dark enough to maintain the ratio.
2. Original and sale price pairs
Product pages often show an original price in light gray with a strikethrough next to a red sale price. Both values need to meet contrast requirements. The original price is frequently the one that fails because it is intentionally de-emphasized.
/* Sale price display */
<p class="price-block">
<span class="price-original">$95.00</span>
<span class="price-sale">$67.00</span>
</p>
/* CSS -- original price must still meet 4.5:1 */
.price-original {
/* Fails: color: #aaaaaa; (1.93:1 on white) */
color: #767676; /* Passes: 4.54:1 */
text-decoration: line-through;
}
.price-sale {
color: #c0392b; /* Check: #c0392b on #ffffff = 4.51:1, just passes */
}
Note that the red used for sale prices is often chosen for brand reasons rather than accessibility. A typical brand red like #e74c3c on white produces 3.88:1, which fails. Darker reds like #c0392b or #a93226 pass at normal text sizes.
3. Ghost buttons
Ghost buttons -- outline-only buttons with transparent backgrounds -- are common on product pages for secondary actions like "Add to wishlist" or "View details." The button text and border both need to meet contrast requirements against whatever background they sit on.
/* Ghost button on white background */
.btn-ghost {
background: transparent;
/* Fails: border and text at #aaaaaa (1.93:1) */
border: 1px solid #aaaaaa;
color: #aaaaaa;
/* Fix: darken both to pass 3:1 (border) and 4.5:1 (text) */
border: 1px solid #767676; /* 4.54:1, satisfies the 3:1 UI component requirement */
color: #595959; /* 7.0:1, clearly passes */
}
/* Ghost button on a dark hero section */
.hero .btn-ghost {
/* Dark background, so the logic reverses */
border: 1px solid rgba(255,255,255,0.7); /* may not pass if background varies */
color: #ffffff; /* 21:1 on #1c3d5a, always passes */
/* Safer: use a solid white border */
border: 1px solid #ffffff;
}
4. Placeholder text in form fields
Browser default placeholder text renders at approximately 50-60% opacity on most browsers, which produces contrast ratios in the range of 1.7:1 to 2.5:1 on white backgrounds. Custom placeholder styling often makes this worse by setting an explicit light gray. Placeholder text fails the 4.5:1 threshold on almost every site that does not explicitly address it.
<label>. Both issues need to be fixed independently.
/* Fix: set placeholder contrast explicitly */
input::placeholder {
/* Browser default is often ~40% opacity; replace with a fixed color */
color: #767676; /* 4.54:1 on white -- just passes */
opacity: 1; /* Override Firefox's default 0.54 opacity on placeholder */
}
/* Or use a slightly darker value for more comfortable reading */
input::placeholder {
color: #595959;
opacity: 1;
}
Setting opacity: 1 on the placeholder selector is important in Firefox, which applies an additional opacity reduction on top of the color value. Without it, a gray that passes on Chrome may fail in Firefox.
5. Footer text and secondary navigation
Site footers frequently use a dark navy or charcoal background with medium gray text for secondary links (privacy policy, terms of service, copyright notice). The combination of a dark background with a slightly lighter gray often hovers around 2:1 to 3:1, which fails.
/* Footer on dark background */
.site-footer {
background: #1a2530;
color: #8899aa; /* Fails: #8899aa on #1a2530 = 2.76:1 */
}
.site-footer a {
color: #8899aa; /* Same failure */
}
/* Fix: lighten the text */
.site-footer {
background: #1a2530;
color: #b8c8d8; /* #b8c8d8 on #1a2530 = 5.4:1 -- passes */
}
.site-footer a {
color: #d0dde8; /* Even lighter for links: 7.1:1 -- passes */
}
6. Cookie consent and marketing banners
Third-party cookie consent banners and promotional banners near the top of the page frequently fail contrast requirements. These are added by JavaScript snippets (Osano, OneTrust, Cookiebot) with their own default styles that the site owner may not think to audit. The banner text, button labels, and links all need to meet contrast requirements.
If you use a consent management platform, check its configuration options for text and button colors. Most platforms let you set brand colors through their admin interface. Verify the resulting contrast ratios before publishing.
7. Product option labels and swatches
Color and size selectors often display option names or quantities in a light gray on a white tile. An unselected size option displayed as #bbbbbb on white has a ratio of 1.54:1. Selected state indicators with colored borders may also fall short of the 3:1 UI component requirement.
/* Size swatch: text label and border both need contrast */
.size-option {
color: #bbbbbb; /* Fails: 1.54:1 */
border: 1px solid #cccccc; /* Fails: 1.34:1 against white */
}
/* Fix: darken both */
.size-option {
color: #595959; /* 7.0:1 -- passes */
border: 1px solid #767676; /* 4.54:1 -- exceeds the 3:1 UI threshold */
}
/* Selected state */
.size-option[aria-pressed="true"],
.size-option.selected {
color: #1c3d5a;
border: 2px solid #1c3d5a; /* Dark enough to pass at any width */
background: #f0f4f8;
}
8. Promotional badges and tags
Sale, New, and Best Seller badges placed over product images often fail. Yellow-on-white produces ratios as low as 1.07:1. Orange on white is better but still often insufficient. These badges need either dark text on a light background or light text on a dark background, with verified ratios.
/* Common badge failures */
.badge-sale {
background: #f6c23e; /* Yellow */
color: #ffffff; /* Fails: white on yellow = 1.7:1 */
}
.badge-new {
background: #f39c12; /* Orange */
color: #ffffff; /* Fails: white on this orange = 2.5:1 */
}
/* Fixes */
.badge-sale {
background: #f6c23e;
color: #1a1a1a; /* Dark text on yellow: 9.6:1 -- passes */
}
.badge-new {
background: #1c3d5a; /* Dark navy */
color: #ffffff; /* White on navy: 10.2:1 -- passes */
}
/* Alternative: darken the badge background enough for white text */
.badge-sale {
background: #b7891a; /* Darker amber */
color: #ffffff; /* White on #b7891a = 4.6:1 -- passes */
}
How to check contrast ratios
Chrome DevTools includes a contrast checker built into the element inspector. Select any text element, click on the color swatch in the Styles panel, and the picker will display the current contrast ratio along with AA and AAA pass/fail indicators. It also shows where on the color spectrum you could move the current foreground color to reach a passing ratio.
Firefox has a similar tool in its Accessibility inspector. Open the Accessibility panel, click on any element in the tree, and the sidebar shows the contrast ratio for that element's text against its computed background.
For checking colors you are planning to use before implementing them, the WCAG contrast ratio formula uses the relative luminance values of the two colors. Most contrast checker tools (standalone apps, browser extensions, or online calculators) accept hex values and return the ratio with pass/fail status. Enter your planned text and background hex codes and verify before writing the CSS.
What automated scanning catches
Automated scanners can detect contrast failures reliably when text sits on a solid, uniform background. The scanner computes the foreground color from the element's computed CSS text color and the background color from the computed background of the element or its ancestors. This works for the most common patterns: body text, button labels, navigation links, form labels, and placeholder text on solid backgrounds.
Scanners cannot reliably detect contrast failures when:
- Text sits on a gradient background (the background color varies across the element)
- Text overlays a photograph or hero image (the effective background depends on which part of the image appears behind the text)
- Background is set via a CSS variable that resolves differently in different contexts
- Text color and background color are both set via opacity or RGBA values on nested elements (the computed luminance depends on stacking)
If your site uses text on images or gradient backgrounds, manual verification with a color picker tool is needed for those specific areas, even after a passing automated scan.
Fixing contrast without redesigning your brand
A common objection to contrast fixes is that the brand color palette was professionally designed and changing it will break the visual identity. In practice, contrast requirements rarely require changing primary brand colors. The failures almost always come from secondary elements: de-emphasized text, ghost button states, placeholder text, footer links. These are candidates for darkening without touching primary brand colors.
When a primary button or hero element does use a brand color that fails, the fix is usually to darken the shade by 10-20% rather than change the hue entirely. A shift from #e74c3c to #c0392b keeps the red family while pushing the ratio over the threshold. Run the adjusted hex through a contrast checker to verify before committing to the change.
If the brand requires a color that cannot meet contrast requirements at any shade (yellow, gold, bright cyan), the alternative is to use dark text on that background rather than white. Dark text on yellow passes; white text on yellow does not.
Checking your store
Start by running a keyboard-free visual scan: open your product page, cart, and checkout in a browser and look for any text that is light gray or a muted color on a white or light background. Price text, placeholder text, ghost button labels, and footer links are the most likely candidates. Then use DevTools to check the contrast ratio of anything that looks borderline.
If you want a full scan that flags contrast failures across your entire site alongside other WCAG issues, request a free accessibility scan and we will run your store and send you the results.