← Back to Accessalyze

By Genesis AI Services · April 21, 2026 · 8 min read · CSS & Color

How to Fix Color Contrast Issues in CSS

The ratios: Normal text on its background needs 4.5:1. Large text (18px+ regular or 14px+ bold) needs 3:1. UI component borders and focus rings need 3:1. These come from WCAG criteria 1.4.3 and 1.4.11.

More than 80% of audited websites have color contrast failures. The good news: once you know the rules, most fixes are a single CSS variable change. Here are the most common failure patterns and their fixes.

Fix 1 — Body Text Too Light

/* FAIL: gray-400 (#9ca3af) on white = 2.85:1 */
body { color: #9ca3af; }

/* FAIL: gray-500 (#6b7280) on white = 4.54:1 — barely passes */
body { color: #6b7280; }

/* PASS (safe): gray-700 (#374151) on white = 8.59:1 */
body { color: #374151; }

/* PASS (better): near-black on white = 19.5:1 */
body { color: #111827; }

For body text, aim for at least gray-600 (#4b5563, 7.16:1). Gray-500 technically passes but has no margin for slightly off-white backgrounds.

See how 321 websites scored →

View the 2026 Report

Fix 2 — Secondary/Muted Text

Muted text (metadata, captions, helper text) is a common failure area because designers want it subtle.

/* FAIL: caption text at #aaa on white = 2.32:1 */
.caption { color: #aaaaaa; }

/* PASS: minimum passing gray for normal text on white */
.caption { color: #767676; } /* 4.54:1 */

/* Better: more breathing room */
.caption { color: #6b7280; } /* 4.54:1, Tailwind gray-500 */

Fix 3 — Blue Links on White Background

/* FAIL: Tailwind blue-400 = 2.85:1 */
a { color: #60a5fa; }

/* FAIL: Tailwind blue-500 = 3.35:1 (passes large text, not normal) */
a { color: #3b82f6; }

/* PASS: Tailwind blue-600 = 5.08:1 ✓ */
a { color: #2563eb; }

/* PASS: Tailwind blue-700 = 7.27:1 */
a { color: #1d4ed8; }

Fix 4 — White Text on Colored Backgrounds

White (#ffffff) text requires very dark backgrounds to pass:

/* White text needs these minimum shades: */
/* Blue: blue-600 (#2563eb) = 5.08:1 ✓ */
.btn-blue { background: #2563eb; color: #fff; }

/* Green: green-700 (#15803d) = 5.74:1 ✓ */
.btn-green { background: #15803d; color: #fff; }

/* Red: red-700 (#b91c1c) = 5.96:1 ✓ */
.btn-danger { background: #b91c1c; color: #fff; }

/* Gray: gray-700 (#374151) = 8.59:1 ✓ */
.btn-secondary { background: #374151; color: #fff; }

/* FAIL: orange-500 (#f97316) with white = 2.43:1 */
/* Use dark text on orange instead: */
.badge-orange { background: #f97316; color: #1c1917; } /* 8.2:1 ✓ */

Fix 5 — Placeholder Text

/* FAIL: default placeholder is usually #aaa = 2.32:1 */
::placeholder { color: #aaaaaa; }

/* PASS: minimum */
::placeholder { color: #767676; }

/* Practical: add slightly higher contrast + italic to signal it's a hint */
::placeholder {
  color: #6b7280;
  font-style: italic;
}

Fix 6 — Disabled Inputs

WCAG exempts disabled controls from contrast requirements — but "disabled" must mean truly non-interactive, not just visually dimmed. If a user can still focus or interact with it, contrast requirements apply.

/* OK for truly disabled states */
input:disabled {
  color: #9ca3af;
  background: #f3f4f6;
  cursor: not-allowed;
}

Fix 7 — Focus Rings

/* FAIL: default browser outline removed */
:focus { outline: none; }

/* FAIL: thin light outline */
:focus-visible { outline: 1px dotted #ccc; }

/* PASS: 3:1+ contrast against adjacent background */
:focus-visible {
  outline: 3px solid #2563eb; /* 5.08:1 against white */
  outline-offset: 3px;
  border-radius: 4px;
}

Fix 8 — Dark Mode

@media (prefers-color-scheme: dark) {
  body {
    background: #0f172a; /* slate-900 */
    color: #e2e8f0;      /* slate-200, 14.4:1 ✓ */
  }
  a {
    color: #93c5fd; /* blue-300, 5.15:1 on slate-900 ✓ */
  }
  .muted {
    color: #94a3b8; /* slate-400, 4.65:1 on slate-900 ✓ */
  }
}

CSS Custom Properties Strategy

Define all colors as custom properties with contrast metadata in comments. This makes finding and fixing failures much easier:

:root {
  /* All contrast ratios are against white (#fff) */
  --color-text-primary: #111827;    /* 19.5:1 ✓ */
  --color-text-secondary: #374151; /* 8.59:1 ✓ */
  --color-text-muted: #6b7280;     /* 4.54:1 ✓ (minimum) */
  --color-link: #2563eb;           /* 5.08:1 ✓ */
  --color-link-hover: #1d4ed8;     /* 7.27:1 ✓ */
  --color-placeholder: #767676;    /* 4.54:1 ✓ (minimum) */
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
  :root {
    --color-text-primary: #f1f5f9;   /* 16.6:1 on #0f172a ✓ */
    --color-text-secondary: #e2e8f0; /* 14.4:1 on #0f172a ✓ */
    --color-text-muted: #94a3b8;     /* 4.65:1 on #0f172a ✓ */
    --color-link: #93c5fd;           /* 5.15:1 on #0f172a ✓ */
  }
}

Find All Contrast Failures on Your Site

Accessalyze scans every color combination on your live pages and reports the exact failing ratios with suggested fix values.

Scan for Contrast Issues →

← Back to Accessalyze · ← WCAG Contrast Requirements

Accessalyze - Free WCAG 2.1 scanner that writes the fix code for you | Product Hunt

See real website accessibility scores: Browse 244+ free accessibility audits →

Try it yourself

Enter your website URL to get a free accessibility score.

Check your website accessibility score free Scan Now →