Designers tend to know the basic rule: WCAG 2.2 AA requires 4.5:1 contrast for normal text and 3:1 for large text (≥18px, or 14px bold). What they don't always know is that the body text usually passes. The failures that show up in real audits are in the corners of the design — the hover state nobody tested, the placeholder pretending to be a label, the white text on a photograph that works on the hero crop but breaks on mobile.
Here are the cases I see fail repeatedly, in roughly the order I see them. For each one I'll cite the WCAG criterion that catches it and describe the fix that actually works in production.
1. Hover and active states that lighten
The default state of the button passes contrast at 4.7:1. On hover, the design system lightens the background by 10% to "indicate interactivity". On hover, the contrast drops to 3.2:1. The button now fails AA in its active interaction state — the moment when contrast matters most, because the user is committing to an action.
Why this happens: hover-as-lightening is a deeply ingrained convention from the print era of Microsoft Office and the early web. It treats hover as "secondary" to the primary state. But hover is when the user is most engaged and most needs visual confirmation.
Fix: on dark-on-light buttons, hover should usually darken the background, not lighten it. The instinct that says "lightening = active" is wrong; what you actually want is "more contrast = active". Test both default and hover with the contrast checker, not just default.
WCAG reference: 1.4.3 Contrast (Minimum) applies to text in all states.
2. Placeholder text used as a label
The form input has no visible label. The placeholder reads "Email address". When the user starts typing, the placeholder disappears, taking the only label with it. By the time they get to "Postcode" three fields later, they have no idea which field they're in.
This pattern fails on multiple criteria simultaneously:
- 3.3.2 Labels or Instructions — placeholders aren't labels. WCAG is explicit that the field needs a persistent label.
- 1.4.3 Contrast (Minimum) — most placeholders are styled at low contrast (often #999 on #FFF, which is 2.8:1). For users with low vision, the placeholder is illegible before they start typing.
- 3.3.5 Help — re-discovering the label requires deleting all input, which is destructive.
Fix: persistent visible labels above every input. Reserve placeholders for example values ("e.g. you@studio.com") that don't carry the meaning of the field.
3. Text on imagery (the gradient that isn't there)
The hero image contains a soft sky and a complex landscape. The white text is positioned in the upper third where the sky is. The contrast against the sky is 5.2:1, the design passes review. The site goes live, and on a smaller screen the image crops differently — the text now overlaps the landscape, where contrast drops to 1.8:1. The user can't read the headline.
Variations of this:
- Different image at different breakpoints, contrast tested only on one.
- User-uploaded imagery (avatars, marketing modules with editable images) with no enforced contrast on the overlay text.
- The "we'll fix it with a gradient" pattern, where the gradient is too subtle to actually rescue contrast.
Fix: three options, in increasing order of robustness:
- Solid scrim. A semi-transparent solid block behind the text. Works at all crops.
- Test the worst case. Pick the darkest pixel the text could ever overlap, and contrast-test against that.
- Don't put text over imagery at all. Put the text alongside or below. This is the move most premium publications have made.
WCAG reference: 1.4.3 Contrast (Minimum). Tools like our contrast checker handle the maths but can't tell you which pixel the text will overlap — that's still a designer call.
4. Disabled buttons (the "fine print" failure)
Disabled buttons are styled at 30% opacity to "communicate disabled-ness". The contrast against the button background is now 1.5:1. The button label is essentially unreadable.
This one is genuinely tricky. WCAG 1.4.3 has a famous exception: "Text or images of text that are part of an inactive user interface component… have no contrast requirement." So technically, disabled buttons can fail contrast and still pass AA.
But: just because it's technically compliant doesn't make it usable. A disabled button is information — it's telling the user "you can't do this thing yet". If the user can't read what the button says, they don't know what they're being prevented from doing.
Fix: aim for 3:1 on disabled state, even though it's not required. Or better — replace the disabled button entirely. If the user can't proceed, why is the button there? Often the better UX is an enabled button with a tooltip explaining the missing prerequisite.
5. Focus rings that disappear on light backgrounds
The design system specifies a focus ring of 2px solid blue. It looks great on the dark navigation. But on a white card with a light blue background, the focus ring blends in. Keyboard users lose their position the moment they tab into the card.
Variations:
- Focus ring blue is too close to the brand blue used in surrounding UI.
- Focus ring is the same colour as the input border, making focus indistinguishable from rest state.
outline: noneapplied without a replacement (the all-time most common offender).
WCAG reference: 2.4.7 Focus Visible (AA). New in 2.2: 2.4.13 Focus Appearance (AAA) which adds size and contrast minimums to the focus indicator itself.
Fix: use a focus indicator that combines outline + offset shadow + colour shift, so at least one of those modes is visible against any surrounding background. The Material 3 and Apple HIG guidelines for focus rings are both worth borrowing.
6. Charts and data visualisations
The line chart has six different lines colour-coded by category. They look great against the white background. They're indistinguishable for users with red-green colour deficiency. The legend says "blue line, red line, green line" which is meaningless if you can't tell the difference between blue, red and green.
WCAG reference: 1.4.1 Use of Color. This isn't strictly a contrast failure — it's a "colour-as-only-distinguisher" failure. Same family of problem, different criterion.
Fix: always pair colour with at least one other distinguisher. Line styles (solid/dashed/dotted), markers (circles/triangles/squares), or — best — direct labels on the lines themselves. Direct labels also remove the need for users to look back and forth between chart and legend, which is good for everyone.
7. Error messages that rely on red text
The form field is fine until the user submits. Then the field outline turns red, and red error text appears below. Users with colour deficiency see no change in the field outline. Users on a screen reader hear no announcement because the error wasn't wrapped in an aria-live region.
Three failures in one:
1.4.1 Use of Color— colour alone communicates the error state.3.3.1 Error Identification— error must be programmatically associated with the field.4.1.3 Status Messages— status messages must be conveyed to assistive tech.
Fix: error icon next to the field (visual non-colour cue), error text wrapped in role="alert" or aria-live="polite", and the field's aria-describedby pointing to the error.
How to actually catch these in your process
Three habits worth building:
- Test all states, not just the default. When you contrast-check a button, also check hover, focus, active, disabled and any selected/pressed states.
- Test against the worst-case pixel. For text on imagery, pick the darkest or lightest spot the text could ever overlap and test that. Don't test the average.
- Run a check before handover. Once components are built, run the contrast checker on each variant. Ours covers the maths in seconds.
Most contrast failures aren't ignorance — they're the consequence of testing one happy path and assuming everything else inherits it. The audits catch the corners. Build the corner-checking into your handover ritual and most of these stop happening.