Field borders, icons, focus rings and chart marks need 3:1 contrast. See which controls on public pages fall short and how to fix them.
Two kinds of visual information need a contrast ratio of at least 3:1 against the adjacent colors: user interface components, and graphical objects. For a component, that means the parts you need in order to find the control and tell what state it is in, such as the edge of a text field, the box of a checkbox, a selected tab marker or a focus indicator. For graphics, it means the parts needed to understand the content, such as the lines in a chart or a stand-alone icon.
Not everything is covered. Disabled controls, appearance set by the browser and left unchanged, hover effects, purely decorative graphics, and graphics that only repeat information already in nearby text are exempt. Logos and photographs are exempt where the exact appearance is essential. Text inside graphics is judged by the text contrast criterion instead.
Pale gray field borders, white-on-light-blue icons and focus rings in a barely different shade are common on government and school sites, often because a design system was tuned for a minimalist look. A resident who cannot see where to type, or which page of results is current, has no clear way forward, however good the text contrast is.
The most useful habit is to ask one question per control: if the text were removed, could I still find this and tell whether it is on, off, selected or focused? That question catches search boxes, pagination, toggles and calendar day pickers on a county or school site that look fine in a style guide.
A field that is just a slightly gray rectangle on a white page has a boundary with a ratio of about 1.12:1. The edge needs a 3:1 border, or another cue that is itself at least 3:1.
.search input {
background: #f2f2f2; /* 1.12:1 against the page */
border: 0;
}.search input {
background: #ffffff;
border: 2px solid #6b6b6b; /* 5.33:1 against the page */
}In a chart, the marks that carry the data need 3:1 against the background and, where it matters, against each other. Pale fills on white vanish, and pale neighbors blend together.
.bar { fill: #c6dbef; } /* 1.42:1 on white */.bar { fill: #2171b5; } /* 5.13:1 on white */
/* also label each bar with its value, so color is not the only cue */Where adjacent areas of similar color cannot be avoided, separate them with a line that does meet 3:1.
A focus ring is a user interface component state, so it needs 3:1 against what is behind it. A mid-blue ring on a navy header can drop below that. A white or light ring on a dark header is a simple fix. The browser's default focus ring is exempt only if you have not changed it.
.site-header a:focus-visible {
outline: 2px solid #1565c0; /* 2.68:1 on #0b2545 */
}.site-header a:focus-visible {
outline: 3px solid #ffd54f; /* 10.9:1 on #0b2545 */
outline-offset: 2px;
}Text field outlines in native apps follow the same rule. Light hairlines that look neat in a mockup are hard to find outdoors.
// SwiftUI
TextField("Parcel number", text: $parcel)
.overlay(RoundedRectangle(cornerRadius: 8)
.stroke(Color(white: 0.85))) // about 1.4:1 on white// SwiftUI
TextField("Parcel number", text: $parcel)
.overlay(RoundedRectangle(cornerRadius: 8)
.stroke(Color(white: 0.45), lineWidth: 1.5)) // about 4.7:1 on white// Jetpack Compose
OutlinedTextField(
value = parcel, onValueChange = { parcel = it },
label = { Text("Parcel number") },
colors = OutlinedTextFieldDefaults.colors(
unfocusedBorderColor = Color(0xFFD0D0D0)) // 1.54:1
)// Jetpack Compose
OutlinedTextField(
value = parcel, onValueChange = { parcel = it },
label = { Text("Parcel number") },
colors = OutlinedTextFieldDefaults.colors(
unfocusedBorderColor = Color(0xFF757575)) // 4.61:1
)Ship it when:
The text counterpart is the criterion on minimum contrast. For the wider set of issues, see common barriers.