Input validation a11y
Skill almasumdev/awesome-mobile-accessibility-agent-skills/.github/skills/forms/input-validation-a11y
Announcing validation results, shaping error messages, and recovering focus. Use this when implementing client-side validation or async server-side errors in forms.From its SKILL.md
npx -y skills add almasumdev/awesome-mobile-accessibility-agent-skills --skill input-validation-a11yAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.8 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it
Input Validation Accessibility
Instructions
Validation is the most frequent reason a user hears silence from the screen reader while the UI lights up red. Make validation visible and audible, and make recovery obvious.
1. When to Validate
- On blur for per-field format errors (invalid email, bad phone).
- On submit for completeness ("Name required", "Accept terms required").
- Live as-you-type only for password strength meters and character counts — always additive, never blocking.
Avoid validating on every keystroke — the stream of announcements is unusable with VoiceOver / TalkBack.
2. Error Message Rules
- Say what is wrong and how to fix it: "Enter a valid email address, for example [email protected]".
- Avoid vague messages: "Invalid input", "Try again".
- Use the field label: "Email is required" (not "This field is required").
- Keep it short — screen readers read it verbatim.
3. Live Region Announcements
When an error appears, announce it automatically without stealing focus.
iOS (SwiftUI):
Text(error ?? "")
.foregroundStyle(.red)
.accessibilityHidden(error == nil)
.onChange(of: error) { _, new in
if let msg = new { UIAccessibility.post(notification: .announcement, argument: msg) }
}
UIKit — set the error label's accessibility trait:
errorLabel.accessibilityTraits = .staticText
errorLabel.text = message
UIAccessibility.post(notification: .announcement, argument: message)
Compose:
if (error != null) {
Text(
text = error,
color = MaterialTheme.colorScheme.error,
modifier = Modifier.semantics { liveRegion = LiveRegionMode.Assertive }
)
}
Flutter:
SemanticsService.announce('Email is required', Directionality.of(context));
RN:
<Text
accessibilityRole="alert"
accessibilityLiveRegion="assertive"
style={styles.error}>{error}</Text>
Use polite for non-blocking warnings (password strength moving to "weak"), assertive for blocking errors only.
4. Associating Errors With Fields
Screen readers should read the error as part of the field.
UIKit:
emailField.accessibilityLabel = "Email"
emailField.accessibilityValue = email
emailField.accessibilityHint = error ?? ""
Better — use a combined label so the reader announces "Email, required. Error: must include @":
emailField.accessibilityLabel = "Email, required. \(error.map { "Error: \($0)" } ?? "")"
Compose:
OutlinedTextField(
value = email,
onValueChange = ::onChange,
label = { Text("Email") },
isError = error != null,
supportingText = { if (error != null) Text(error) },
modifier = Modifier.semantics {
if (error != null) this.error(error)
}
)
The error(...) semantics property tells TalkBack the field is invalid and reads the message.
Flutter — TextFormField already sets semantics when errorText is non-null.
RN:
<TextInput
accessibilityLabel="Email"
accessibilityInvalid={!!error}
aria-errormessage="emailError" /* web */ />
<Text nativeID="emailError" accessibilityLiveRegion="polite">{error}</Text>
5. Error Summary on Submit
For long forms (signup, checkout), show a summary at the top on submit failure and move focus there, or to the first invalid field.
// SwiftUI
if !errors.isEmpty {
VStack(alignment: .leading) {
Text("Please fix the following:").font(.headline)
ForEach(errors) { err in
Button(err.message) { focus = err.field } // jump to field
}
}
.accessibilityElement(children: .contain)
.accessibilityAddTraits(.isHeader)
.onAppear { announceScreenChange() }
}
Move focus to the summary heading so the reader picks it up.
6. Async / Server-Side Errors
- Network errors: "Couldn't sign in. Check your connection and try again." — actionable, no jargon.
- Server validation: map codes to human text, reuse the same a11y pattern (live region + field association).
- Don't lose the user's input on network failure.
7. Success States
Also announce success so AT users know the flow moved forward.
Snackbar(
modifier = Modifier.semantics { liveRegion = LiveRegionMode.Polite }
) { Text("Profile saved") }
8. Password Strength Meters
- Announce strength changes via a polite live region only when the bucket changes (weak → medium → strong), not on every keystroke.
- Provide text alongside the visual bar (never bar-only).
9. Character Counters
For length-limited fields, expose the counter via accessibilityValue and update live as typing progresses — but throttle to announce every 10 characters or on pause, not every keystroke.
TextField(
maxLength: 280,
decoration: InputDecoration(counterText: '$length of 280'),
)
10. Common Pitfalls
- Red border only, no text.
- Error text present but not announced.
- Focus jumps to top on submit instead of to the summary or first error.
- Live region re-announces every keystroke.
- Error message contains the wrong field name.
- Success banner never announced; AT user doesn't know the save worked.
- Server error returns HTTP status code to the user ("Error 422").
Checklist
- Each error paired with an explanation and a recovery hint.
- Errors announced via live region (polite or assertive as appropriate).
- Field's a11y state set to invalid /
error()while message is present. - On submit failure, focus moves to summary or first invalid field.
- Success states announced politely.
- Character counters / password meters announce on bucket change, not per key.
- User's input preserved on network errors.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.