Androidforms-feedbackBeginner

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

  1. 1Enable Screen Reader:
  2. 2Navigate through form: Swipe through each field and verify labels are announced clearly
  3. 3Test helper text: Ensure guidance and requirements are read aloud when focusing on fields
  4. 4Trigger validation errors: Submit incomplete or invalid data and verify errors are announced immediately
  5. 5Check error clarity: Ensure error messages are specific and explain how to fix the issue
  6. 6Test error recovery: Correct errors and verify success states are announced
  7. 7Verify grouping: Confirm related fields are announced as a group with appropriate context
  8. 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