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:
- ✅ The input element correctly has
aria-invalid="true"
- ❌ The input element is missing
aria-describedby linking to the error message
- ❌ 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
- Visit the demo URL
- Click "Submit" to trigger validation errors
- Open browser DevTools (F12)
- Inspect the email or password
<input> element
- 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.
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
TextFormFielddisplays a validation error:aria-invalid="true"aria-describedbylinking to the error messageCurrent 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 isExpected 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 correctedLive Demo
Before (current behavior): https://flutter-demo-04-before.web.app
Steps to Reproduce
<input>elementaria-invalid="true"is present, butaria-describedbyis missingWCAG 2.2 Requirements
This issue affects compliance with:
Reference: WCAG 2.2 Understanding Error Identification
Proposal
Proposed Solution
This would require coordinated changes across the framework and engine:
1. Framework: Add
describedByProperty to SemanticsFile:
packages/flutter/lib/src/semantics/semantics.dartAdd 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.dartModify
_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
describedByThrough Update PipelineFile:
lib/ui/semantics.dartAdd parameter to
SemanticsUpdateBuilder.updateNode():void updateNode({
// ... existing parameters ...
List? describedBy,
});### 4. Engine Web: Render
aria-describedbyFile:
lib/web_ui/lib/src/engine/semantics/semantics.dartStore and track the
describedByreferences, 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
hint→aria-description, a separate concern)Impact
Additional Context
This is distinct from PR #169157, which correctly implements
aria-descriptionfor thehintproperty. Thehintproperty is for supplementary instructional text, whileerrorTextis for validation feedback that must be linked to the control.SemanticsProperties.hintaria-description(inline string)InputDecoration.errorTextaria-describedby(element ID reference)Both patterns are valid but serve different purposes per ARIA specifications.