Name the field in error and say what is wrong in text, not only with color. Examples from enrollment and booking forms.
A form that comes back with a red border and no explanation leaves many people stuck: a blind user may not know anything failed, a color-blind user may not see the border, and anyone can be unsure what was wrong. WCAG 2.1 success criterion 3.3.1 says that if an input error is automatically detected, the item in error is identified and the error is described to the user in text.
"Input error" covers both required information left blank and information in the wrong format or outside the allowed values. How you show the message is up to you: inline next to the field, in a summary at the top, or in an alert. Color and icons can support the text but cannot replace it. If the page automatically changes a value, such as capping a number, the user also needs to be told in text.
Saying how to fix the error belongs to 3.3.3, though one message often covers both. The programmatic association of a message with its field is mostly a matter of other criteria such as 1.3.1 and 4.1.2. This page deals with whether an error is identified at all and described in text.
DOJ's web accessibility guidance names inaccessible forms among the common barriers, including missing error alerts. For a DMV appointment, a school enrollment packet or a county clinic booking, an error that is only visual means the resident cannot finish an essential service on their own. More on this in common barriers.
<label for="child-dob">Child's date of birth</label>
<input id="child-dob" class="field invalid">
<style>.invalid { border: 2px solid #d00; }</style><label for="child-dob">Child's date of birth</label>
<input id="child-dob" aria-invalid="true" aria-describedby="child-dob-err">
<p id="child-dob-err" class="error">
Child's date of birth: enter a date, for example 04/17/2018.
</p><div class="error-box">Please fix the highlighted fields.</div><div role="alert" class="error-box">
<h2>2 problems found</h2>
<ul>
<li><a href="#guardian">Guardian name: enter a name</a></li>
<li><a href="#district-zip">ZIP code: enter 5 digits</a></li>
</ul>
</div>// Party size above 8 is quietly set to 8
if (size.value > 8) size.value = 8;if (size.value > 8) {
size.value = 8;
sizeNote.textContent = "Party size changed to 8, the maximum for one site.";
}// SwiftUI
TextField("ZIP code", text: $zip)
.overlay(RoundedRectangle(cornerRadius: 6)
.stroke(zipInvalid ? Color.red : Color.gray))// SwiftUI
VStack(alignment: .leading) {
TextField("ZIP code", text: $zip)
if zipInvalid {
Text("ZIP code: enter 5 digits.")
.foregroundColor(.red)
.accessibilityLabel("Error. ZIP code: enter 5 digits.")
}
}// Jetpack Compose
OutlinedTextField(
value = zip, onValueChange = { zip = it },
label = { Text("ZIP code") },
isError = zipInvalid // red outline only
)// Jetpack Compose
OutlinedTextField(
value = zip, onValueChange = { zip = it },
label = { Text("ZIP code") },
isError = zipInvalid,
supportingText = { if (zipInvalid) Text("ZIP code: enter 5 digits.") },
modifier = Modifier.semantics { if (zipInvalid) error("Enter 5 digits") }
)aria-invalid and linked descriptions exist but not whether the message makes sense. Read the messages.