Skip to content

[web] Form validation errors not linked to inputs via aria-describedby (WCAG 2.2 compliance) #180496

@flutter-zl

Description

@flutter-zl

Use case

Description

Form validation error messages in Flutter Web are not semantically linked to their corresponding input fields via aria-describedby, which is required for WCAG 2.2 compliance (Success Criteria 3.3.1 and 3.3.3).

Current Behavior

When a TextFormField displays a validation error:

  1. ✅ The input element correctly has aria-invalid="true"
  2. ❌ The input element is missing aria-describedby linking to the error message
  3. ❌ Screen readers announce "invalid" but don't read the error message with the input

Current DOM structure:

Email is required **Screen reader experience:** - Focus on Email field → "Email, text field, invalid" - Error message is announced separately (if at all) - User cannot programmatically discover what the error is

Expected Behavior

The input should be semantically linked to its error message:

Email is required **Expected screen reader experience:** - Focus on Email field → "Email, text field, invalid. Email is required" - Error is announced together with the input context - User immediately understands what needs to be corrected

Live Demo

Before (current behavior): https://flutter-demo-04-before.web.app

Steps to Reproduce

  1. Visit the demo URL
  2. Click "Submit" to trigger validation errors
  3. Open browser DevTools (F12)
  4. Inspect the email or password <input> element
  5. Notice: aria-invalid="true" is present, but aria-describedby is missing

WCAG 2.2 Requirements

This issue affects compliance with:

  • SC 3.3.1 Error Identification (Level A): Error messages must be programmatically associated with the relevant form control
  • SC 3.3.3 Error Suggestion (Level AA): When an input error is detected, suggestions for correction should be provided and linked to the control

Reference: WCAG 2.2 Understanding Error Identification

Proposal

Proposed Solution

This would require coordinated changes across the framework and engine:

1. Framework: Add describedBy Property to Semantics

File: packages/flutter/lib/src/semantics/semantics.dart

Add a new property to SemanticsProperties:

/// A set of [SemanticsNode.identifier]s that describe this node.
///
/// On web, this translates to aria-describedby pointing to the
/// DOM elements of the referenced semantics nodes.
///
/// This is used to semantically link form controls to their
/// error messages, help text, or other descriptive content.
final Set? describedBy;### 2. Framework: Link Inputs to Error Messages

File: packages/flutter/lib/src/material/input_decorator.dart

Modify _HelperError._buildError() to assign an identifier:

Widget _buildError() {
return Semantics(
container: true,
identifier: 'input-error-${widget.hashCode}', // Assign stable ID
liveRegion: !MediaQuery.supportsAnnounceOf(context),
child: /* error widget */,
);
}And update the input field's semantics to reference it:

Semantics(
textField: true,
describedBy: hasError
? {'input-error-${hashCode}'} // Link to error
: null,
child: /* input widget */,
)### 3. Engine: Pass describedBy Through Update Pipeline

File: lib/ui/semantics.dart

Add parameter to SemanticsUpdateBuilder.updateNode():

void updateNode({
// ... existing parameters ...
List? describedBy,
});### 4. Engine Web: Render aria-describedby

File: lib/web_ui/lib/src/engine/semantics/semantics.dart

Store and track the describedBy references, then render the attribute:

void _updateDescribedBy() {
if (describedByNodes.isNotEmpty) {
// Resolve semantics node IDs to DOM element IDs
final ids = describedByNodes
.map((nodeId) => owner.getNodeById(nodeId)?.element.id)
.whereType()
.join(' ');
element.setAttribute('aria-describedby', ids);
} else {
element.removeAttribute('aria-describedby');
}
}## Related Issues/PRs

Impact

  • Severity: High - affects accessibility compliance for all Flutter Web forms
  • Users affected: Screen reader users, keyboard-only users, users with cognitive disabilities
  • Platforms: Web only (native platforms handle this differently)

Additional Context

This is distinct from PR #169157, which correctly implements aria-description for the hint property. The hint property is for supplementary instructional text, while errorText is for validation feedback that must be linked to the control.

Use Case Property ARIA Pattern
Hint text SemanticsProperties.hint aria-description (inline string)
Validation errors InputDecoration.errorText aria-describedby (element ID reference)

Both patterns are valid but serve different purposes per ARIA specifications.

Metadata

Metadata

Assignees

Labels

P1High-priority issues at the top of the work lista: accessibilityAccessibility, e.g. VoiceOver or TalkBack. (aka a11y)team-webOwned by Web platform teamtriaged-webTriaged by Web platform team

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions