Skip to content

[web] Pass form validation errors to screen readers via aria-description#180556

Merged
auto-submit[bot] merged 22 commits intoflutter:masterfrom
flutter-zl:issue_180496
Feb 18, 2026
Merged

[web] Pass form validation errors to screen readers via aria-description#180556
auto-submit[bot] merged 22 commits intoflutter:masterfrom
flutter-zl:issue_180496

Conversation

@flutter-zl
Copy link
Contributor

@flutter-zl flutter-zl commented Jan 6, 2026

Fixes part of #180496

Summary

Pass form validation errors to screen readers via aria-description on text fields.

Before:
https://flutter-demo-04-before.web.app/

After:
https://flutter-demo-04-after.web.app/

Limitations & Future Work

This handles string-based errors (errorText). For more complex cases(brought in #180496 (comment)), a follow-up implementation using aria-describedby with element IDs is tracked in #180496:

  • Custom error widgets (InputDecoration.error)
  • Errors outside InputDecoration
  • Custom announcement ordering

The aria-description approach (current) and aria-describedby approach (future) can coexist per ARIA specifications.

Related

@github-actions github-actions bot added a: text input Entering text in a text field or keyboard related problems framework flutter/packages/flutter repository. See also f: labels. engine flutter/engine related. See also e: labels. f: material design flutter/packages/flutter/material repository. a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) platform-web Web applications specifically labels Jan 6, 2026
@flutter-zl flutter-zl marked this pull request as ready for review January 12, 2026 01:06
// Note: The visual hintText is already shown as placeholder, so we
// only need to expose it via semantics when there's no error.
final String? errorText =
widget.decoration?.errorText ?? widget.decoration?.error?.toString();
Copy link
Contributor

@chunhtai chunhtai Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

widget toString() contains debug message and can be potentially nonsensical string in release mode. we can't use it as hint. In case they provide an error widget, we should consider let developer provide one string that represent the error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch. updated.

// only need to expose it via semantics when there's no error.
final String? errorText =
widget.decoration?.errorText ?? widget.decoration?.error?.toString();
final String? semanticsHint = errorText ?? widget.decoration?.hintText;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ignores hinttext if error text is presented. I would guess they should concatenate together. Can you double check the native material textfield's behavior if both are present?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

according to this link, When an error is set, it replaces the helper text visually. The current logic errorText ?? hintText matches how native Material handles this : errors override helper/hint text rather than being combined with it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good

@flutter-zl flutter-zl requested a review from chunhtai January 12, 2026 22:02
Copy link
Contributor

@chunhtai chunhtai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think a bit more, setting the semantics of hint text should probably be in InputDecorator widget since that is where the UI are built.

// only need to expose it via semantics when there's no error.
final String? errorText =
widget.decoration?.errorText ?? widget.decoration?.error?.toString();
final String? semanticsHint = errorText ?? widget.decoration?.hintText;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good

// Note: The visual hintText is already shown as placeholder, so we
// only need to expose it via semantics when there's no error.
final String? semanticsHint =
widget.decoration?.errorText ?? widget.decoration?.hintText;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still want to use widget.decoration?.hintText if the widget.decoration?.error widget is present? I imagine the hintText won't be visible in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Updated the logic.

@flutter-zl
Copy link
Contributor Author

Think a bit more, setting the semantics of hint text should probably be in InputDecorator widget since that is where the UI are built.

InputDecorator is the visual decoration layer inside. It doesn't have access to the Semantics wrapper that TextField adds around it. The hint property we're setting becomes aria-description on the element, and that wrapping is done by TextField.

@chunhtai
Copy link
Contributor

It doesn't have access to the Semantics wrapper that TextField adds around it

Why does it need to? It can introduce its own semantics wrapper which should merge with TextField's. I remember TextField's semantics wrapper are built to be able to merge with other just for this reason

@github-actions github-actions bot removed platform-ios iOS applications specifically tool Affects the "flutter" command-line tool. See also t: labels. a: animation Animation APIs a: internationalization Supporting other languages or locales. (aka i18n) platform-fuchsia Fuchsia code specifically f: scrolling Viewports, list views, slivers, etc. f: cupertino flutter/packages/flutter/cupertino repository d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos f: routes Navigator, Router, and related APIs. f: gestures flutter/packages/flutter/gestures repository. platform-linux Building on or for Linux specifically package flutter/packages repository. See also p: labels. a: desktop Running on desktop f: focus Focus traversal, gaining or losing focus f: integration_test The flutter/packages/integration_test plugin team-ecosystem Owned by Ecosystem team c: tech-debt Technical debt, code quality, testing, etc. e: impeller Impeller rendering backend issues and features requests team-android Owned by Android platform team team-engine Owned by Engine team labels Feb 10, 2026
// will address complex cases (custom error widgets, errors outside
// InputDecoration, custom announcement ordering). See
// https://github.com/flutter/flutter/issues/180496#issuecomment-3713178684.
final String? semanticsHint = decoration.errorText;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chunhtai and @mdebbar , I updated the previous logic to this.

Previous logic
final bool hasErrorWidget = decoration.error != null || decoration.errorText != null;
final String? semanticsHint = hasErrorWidget ? decoration.errorText : decoration.hintText;

Issue with previous logic
hintText was entering the semantics tree twice:

  1. Automatically — the Text(hintText) widget inside InputDecorator gets merged up into the text field's semantics via _RenderDecoration's markAsMergeUp.
  2. Explicitly — our Semantics(hint: hintText) wrapper also set it on the same node.
    The semantics framework concatenates both with \n, producing "Search Google Pay\nSearch Google Pay" error in google internal app.

Fix
Only pass errorText (not hintText) in the explicit wrapper, since hintText already has the automatic path.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand the semantics merging logic, but from what you said, does that mean decoration.errorText and hintText will be concatenated now?

There are tests for labelText+hintText and for labelText+errorText. Can we try a test for errorText+hintText?

I'll defer to @chunhtai who knows more about this. Everything else looks good to me!

Copy link
Contributor Author

@flutter-zl flutter-zl Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, errorText and hintText do not concatenate. They end up in separate semantics properties:

  1. hintText → merges into label (via the hint Text widget's RenderParagraph setting config.attributedLabel then markAsMergeUp)

  2. errorText → goes into hint (via our explicit Semantics(hint:) wrapper).

Added a test for errorText+hintText to verify this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change sounds good to me. I will take another review

// will address complex cases (custom error widgets, errors outside
// InputDecoration, custom announcement ordering). See
// https://github.com/flutter/flutter/issues/180496#issuecomment-3713178684.
final String? semanticsHint = decoration.errorText;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand the semantics merging logic, but from what you said, does that mean decoration.errorText and hintText will be concatenated now?

There are tests for labelText+hintText and for labelText+errorText. Can we try a test for errorText+hintText?

I'll defer to @chunhtai who knows more about this. Everything else looks good to me!

Copy link
Contributor

@chunhtai chunhtai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) a: text input Entering text in a text field or keyboard related problems engine flutter/engine related. See also e: labels. f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. platform-web Web applications specifically

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants