Normal text needs 4.5:1 and large text 3:1. See where pale gray text creeps into government pages and how to measure and fix it.
Text, and images of text, need a contrast ratio of at least 4.5:1 against the background. Large text needs at least 3:1. Large means at least 18 point, or at least 14 point if bold; in CSS pixels that is roughly 24 px, or roughly 18.5 px bold.
The thresholds are exact. A result of 4.499:1 fails, so do not round. The ratio comes from the relative luminance of the two colors and runs from 1:1 (no contrast) to 21:1 (black on white).
Exceptions are narrow. Large text has a lower threshold (3:1). Incidental text is exempt: text in an inactive (disabled) control, purely decorative text, text nobody can see, or text that is merely part of a picture containing significant other visual content. Logotypes are exempt too. Everything else counts, including placeholder text, text in hover, focus and visited states, and images of text.
People with low vision, cataracts or color vision differences need the extra luminance difference. So does anyone reading a phone in sunlight, or a tired reader at the end of a shift. Pale gray on white looks refined on a designer's calibrated monitor and disappears everywhere else.
DOJ's web guidance names poor color contrast as a common barrier, and it is also one of the easiest problems to measure. It tends to enter a site through a brand palette that was chosen for print, or through a vendor theme that applies pale gray to secondary text: opening hours, fee notes, deadlines, form hints.
Those secondary lines often hold the information that matters most, such as the cutoff time for a permit application or a document list for enrollment. A page can look fine at the top and fail at the bottom, so check the whole template, not just the header.
Timetable details, platform numbers and delay notes are often set in light gray. Darkening them to a measured value costs nothing.
.departure-note {
color: #9a9a9a; /* 2.81:1 on white, fails */
font-size: 0.875rem;
}.departure-note {
color: #5f5f5f; /* 6.39:1 on white */
font-size: 0.875rem;
}A tinted background lowers the ratio of text that passes comfortably on white. The pair below fails by a narrow margin, which is typical of automated palettes.
.hours-card {
background: #f3f4f6;
color: #6b7280; /* 4.39:1, just under 4.5:1 */
}.hours-card {
background: #f3f4f6;
color: #4b5563; /* 6.87:1 */
}Check every interactive state, not only the resting one. A button can pass at rest and fail when the pointer is over it.
.enroll-btn { background: #1565c0; color: #fff; } /* 5.75:1 */
.enroll-btn:hover { background: #64a0e8; color: #fff; } /* 2.71:1 */.enroll-btn { background: #1565c0; color: #fff; } /* 5.75:1 */
.enroll-btn:hover { background: #0d47a1; color: #fff; } /* 8.63:1 */Orange text on white is a classic failure for "pending" labels. A darker amber such as #a85d00 passes on white at 4.96:1, while #ff9800 manages only 2.16:1. Large text can use the 3:1 threshold, but only when it really is at least 18 point, or 14 point bold.
The criterion applies to text in native apps too. Hard-coded light grays are the usual culprit; prefer semantic or measured colors.
// SwiftUI
Text("Due by April 15")
.foregroundColor(Color(white: 0.6)) // about 2.85:1 on white// SwiftUI
Text("Due by April 15")
.foregroundColor(Color(white: 0.35)) // about 7:1 on white// Jetpack Compose
Text("Due by April 15", color = Color(0xFF999999)) // 2.85:1 on white// Jetpack Compose
Text("Due by April 15", color = Color(0xFF595959)) // 7.0:1 on whiteShip it when:
The same ratio idea, with a different threshold, applies to icons and borders; see the criterion on non-text contrast. For where contrast sits among other issues, read common barriers.