Table of Contents
Designing accessible forms and error feedback
This guide looks at practical ways to design forms that are easy to understand and use for everyone, no matter their abilities.
π― Why It Matters
Forms are vital to many Android applications, but poorly designed forms create frustrating barriers for users with disabilities. Without clear labels, logical grouping, helpful instructions, and accessible error feedback, users relying on screen readers or assistive technologies may struggle to complete essential tasks like registration, checkout, or data entry. Accessible form design ensures all users can input data efficiently and understand validation requirements, creating an inclusive experience that benefits everyone.
π» Implementation
Correct Code Example: Clear Labelling with Material Design
Demo Description
Consider this example of using clear, descriptive labels for form fields:
<!-- Android - Clear, descriptive labels for form fields -->
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email Address">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>Code Explanation
This XML code defines a TextInputLayout with a hint "Email Address" and contains a TextInputEditText for user input. The TextInputEditText is configured to handle email addresses and fills the width of its parent. This provides a clear, floating label that remains visible when the user starts typing.
Correct Code Example: Providing Clear Instructions
Demo Description
This is an example of offering upfront guidance about input requirements:
<!-- Android - Upfront guidance about input requirements -->
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
app:helperText="Must be at least 8 characters long">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>Code Explanation
Code description: This XML code defines a TextInputLayout with a hint "Password" and a helper text "Must be at least 8 characters long". Inside, it contains a TextInputEditText configured for password input which fills the width of its parent. This is important for accessibility.
Correct Code Example: Visual Error Indication
Demo Description
This code sample shows how to implement visual error indication:
<!-- Android - Enable error messages -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/emailInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email Address"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/emailInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>Code Explanation
This XML code defines a TextInputLayout with an ID emailInputLayout and a hint "Email Address." It enables error messages with app:errorEnabled="true". Inside, it contains a TextInputEditText with an ID emailInput, configured for email input and spanning the full width of its parent.
Correct Code Example: Setting and Announcing Errors
Demo Description
Observe how this code sets and announces errors:
// Android - Validate and announce errors
if (!isValidEmail(emailInput.text.toString())) {
emailInputLayout.error = "Please enter a valid email address"
announceError(emailInputLayout)
} else {
emailInputLayout.error = null
}
private fun announceError(view: View) {
val errorText = (view as? TextInputLayout)?.error ?: return
view.announceForAccessibility(errorText)
}Code Explanation
This Kotlin code checks if the email input is valid. If not, it sets an error message on emailInputLayout and announces the error for accessibility. If the email is valid, it clears the error. The announceError function makes the error message accessible by using announceForAccessibility function on the TextInputLayout.
Correct Code Example: Accessible switch with grouped label and custom state announcements
Demo Description
Consider this example of an accessible form switch that uses a single focusable container, exposes the right role/state to TalkBack, and announces clear on/off messages instead of only "on" and "off". XML β Container is the only focusable; label and switch are hidden from accessibility:
<!-- Android - Accessible switch: one focus, clear label and state announcements -->
<LinearLayout
android:id="@+id/switchContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="16dp"
android:minHeight="48dp"
android:screenReaderFocusable="true"
android:focusable="true"
android:clickable="true"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/form_switch_label">
<TextView
android:id="@+id/switchLabel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/form_switch_label"
android:textSize="16sp"
android:importantForAccessibility="no" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/notificationSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="48dp"
android:minHeight="48dp"
android:importantForAccessibility="no" />
</LinearLayout>Code Explanation
This XML and Kotlin code define an accessible switch for forms. The LinearLayout is the single focusable control: it has screenReaderFocusable, focusable, clickable, and contentDescription so TalkBack announces it as one item (e.g. "Subscribe to newsletter, switch, off"). The label and the actual SwitchMaterial use importantForAccessibility="no" so only the container is focused. The delegate sets className, isCheckable, and isChecked so TalkBack treats it as a switch and reports state correctly; roleDescription and text keep the announcement clear. Tapping the container toggles the switch. setOnCheckedChangeListener calls announceForAccessibility with "Subscribed to newsletter" or "Unsubscribed from newsletter" so state changes are announced in full sentences instead of only "on" and "off".
Kotlin
binding.switchContainer.apply {
setOnClickListener {
binding.notificationSwitch.toggle()
}
}
binding.notificationSwitch.setOnCheckedChangeListener { _, isChecked ->
val announcement = if (isChecked) getString(R.string.form_switch_announce_on)
else getString(R.string.form_switch_announce_off)
binding.switchContainer.announceForAccessibility(announcement)
}
ViewCompat.setAccessibilityDelegate(binding.switchContainer, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(
host: View,
info: AccessibilityNodeInfoCompat
) {
super.onInitializeAccessibilityNodeInfo(host, info)
info.roleDescription = "Switch"
info.className = Switch::class.java.name
info.isCheckable = true
info.isChecked = binding.notificationSwitch.isChecked
info.text = getString(R.string.form_switch_label)
}
})β Best Practices
Do's
- βUse clear, descriptive labels for all form fields
- βProvide floating labels that remain visible during input
- βGroup related form elements logically
- βOffer upfront guidance about input requirements
- βProvide both visual and programmatic error feedback
- βAnnounce errors immediately for screen reader users
- βUse helper text to clarify complex requirements
- βClear errors when input becomes valid
- βTest forms with TalkBack (Android) and VoiceOver (iOS)
- βEnsure error messages are specific and actionable
Don'ts
- βUse placeholder text as the only label (it disappears on input)
- βRely solely on color to indicate errors
- βDisplay generic error messages like "Invalid input"
- βShow multiple unrelated fields without logical grouping
- βHide validation requirements until after submission
- βForget to announce error state changes to screen readers
- βUse auto-advance between fields (confusing for assistive tech users)
- βMake required field indicators visual-only (add "required" to labels)
π Common Pitfalls
Placeholder-Only Labels
Using placeholders instead of persistent labels means users lose context once they start typing
Generic Error Messages
"Error" or "Invalid" doesn't help users understand what's wrong or how to fix it
Silent Validation
Errors that appear visually but aren't announced to screen readers leave assistive tech users unaware of problems
Color-Only Indicators
Red borders or text without programmatic error states are invisible to screen readers
Missing Field Relationships
Ungrouped radio buttons or checkboxes confuse screen reader users about their connection
Late Validation Feedback
Waiting until form submission to show errors forces users to hunt for problems
Inconsistent Error Clearing
Leaving error states active after users correct input creates confusion
TalkBack
Announces field labels, input types, helper text, current values, and error states. When errors occur, screen readers should immediately announce the error message. Grouped fields are announced with their group context.
Voice Control
Clear labels enable voice commands like "Tap Email Address" or "Tap Submit Button" for hands-free form completion.
Switch Control
Logical field ordering and grouping help switch users navigate forms efficiently without getting lost in complex layouts.
Dynamic Type
Forms must scale properly when users increase text size. Error messages and helper text should remain readable at all sizes.
Keyboard Navigation
External keyboard users rely on logical tab order. Form fields should be reachable in sequence, and focus indicators must be clearly visible.
π§ͺ Testing Steps
- 1Enable Screen Reader:
- 2Navigate through form: Swipe through each field and verify labels are announced clearly
- 3Test helper text: Ensure guidance and requirements are read aloud when focusing on fields
- 4Trigger validation errors: Submit incomplete or invalid data and verify errors are announced immediately
- 5Check error clarity: Ensure error messages are specific and explain how to fix the issue
- 6Test error recovery: Correct errors and verify success states are announced
- 7Verify grouping: Confirm related fields are announced as a group with appropriate context
- 8Test keyboard navigation: Use external keyboard to tab through form fields in logical order
Become a member to read this solution, and all of Ma11y.
This resource is part of our member knowledge base. Log in or create an account to unlock:
- Complete accessibility guidance and implementation examples
- Curated compliance frameworks and checklists
- Early access to new tools and features
