Skip to content

Conversation

flutter-zl
Copy link
Contributor

@flutter-zl flutter-zl commented May 20, 2025

Summary
This PR improves web accessibility in Flutter by separating hint from label in the semantics engine and exposing them via ARIA attributes. The hint is set using aria-description (or aria-describedby as a fallback for browsers that do not support aria-description).

Details
Hint separation:
The hint is exposed via aria-description if supported, or via aria-describedby with a hidden node as a fallback.
Browser compatibility:
Uses feature detection to choose between aria-description and aria-describedby.
Test coverage:
Added/updated tests to verify correct ARIA attribute behavior and fallback logic.

How to verify
All relevant tests pass (semantics_test.dart, semantics_text_test.dart).

Motivation
This change brings Flutter web closer to accessibility best practices and ARIA standards, improving the experience for users of assistive technologies.

Before/After Change
before: https://before-change-hint.web.app/
after: https://after-change-hint.web.app/
after(fallback to aria-describedby): https://after-change-hint-fallback.web.app/

Issues fixed
#162140

Note
Some focus-related tests (e.g., incrementable sends focus events) are failing, but these failures are also present on the main branch and are unrelated to the ARIA label/hint changes in this PR.

Pre-launch Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement].
  • I signed the [CLA].
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is [test-exempt].
  • I followed the [breaking change policy] and added [Data Driven Fixes] where supported.
  • All existing and new tests are passing.

@github-actions github-actions bot added engine flutter/engine related. See also e: labels. a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) platform-web Web applications specifically labels May 20, 2025
@flutter-zl flutter-zl requested review from yjbanov and mdebbar May 21, 2025 05:01
@flutter-zl flutter-zl marked this pull request as ready for review May 21, 2025 05:15
@flutter-zl flutter-zl requested a review from chunhtai May 21, 2025 16:37
@flutter-zl flutter-zl marked this pull request as draft May 21, 2025 19:50
@flutter-dashboard
Copy link

This pull request has been changed to a draft. The currently pending flutter-gold status will not be able to resolve until a new commit is pushed or the change is marked ready for review again.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

@flutter-zl flutter-zl changed the title Split hint from label and expose it via aria-description in AriaLabelRepresentation Split hint from label and expose it via aria-description or aria-describedby May 22, 2025
@flutter-zl flutter-zl marked this pull request as ready for review May 22, 2025 06:10
@@ -456,6 +462,7 @@ class LabelAndValue extends SemanticBehavior {
}

_getEffectiveRepresentation().update(computedLabel);
_updateHintDescription(semanticsObject.hint);
Copy link
Contributor

Choose a reason for hiding this comment

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

somewhere in the doc of this class should mention hint translate to description

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated.

void _updateHintDescription(String? hint) {
owner.removeAttribute('aria-description');
owner.removeAttribute('aria-describedby');
if (hint != null && hint.trim().isNotEmpty) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (hint != null && hint.trim().isNotEmpty) {
if (hint?.trim().isNotEmpty ?? false) {

Copy link
Contributor

Choose a reason for hiding this comment

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

ignore this if this doesn't promote the hint to non-null.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it does not promote hint to non-null inside the block, so hint is still of type String?, but _setAriaDescriptionOrDescribedBy expects a non-nullable String.

}

@visibleForTesting
static set supportsAriaDescriptionForTest(bool? value) {
Copy link
Contributor

Choose a reason for hiding this comment

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

why nullable value?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Passing null resets _supportsAriaDescription to an uninitialized state.
The backing field _supportsAriaDescription is also nullable (bool?).
This setter is used for testing, to override the browser-detected value.

@@ -446,6 +449,9 @@ class LabelAndValue extends SemanticBehavior {
/// instead.
LabelRepresentation preferredRepresentation;

String? _describedById;
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe convert this to a getter so we we don't need to keep them in sync?

String? get _describedById => _describedBySpan?.id

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 idea. Updated.

_describedBySpan!.setAttribute('hidden', '');
_describedBySpan!.text = hint;
// Attach as a child of the semantics element, not document.body
if (_describedBySpan!.isConnected != true) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I remember a lint would suggest to use ?? instead of equality for checking nullable boolean

Suggested change
if (_describedBySpan!.isConnected != true) {
if (!(_describedBySpan?.isConnected ?? false)) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated.

_describedBySpan = null;
_describedById = null;
final String hintId = 'hint-${semanticsObject.id}';
domDocument.getElementById(hintId)?.remove();
Copy link
Contributor

Choose a reason for hiding this comment

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

what is this for?

Copy link
Contributor Author

@flutter-zl flutter-zl May 22, 2025

Choose a reason for hiding this comment

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

Updated the logic and remove dead code a bit.

_describedBySpan?.remove() and _describedBySpan = null; removes our aria-describedby span and clears the reference.

Comment on lines 551 to 569
if (isSafari) {
final RegExp safariRegexp = RegExp(r'Version\/([0-9]+)\.([0-9]+)');
final RegExpMatch? match = safariRegexp.firstMatch(ui_web.browser.userAgent);
if (match != null) {
final int majorVersion = int.parse(match.group(1)!);
final int minorVersion = int.parse(match.group(2)!);
return majorVersion > 17 || (majorVersion == 17 && minorVersion >= 4);
}
return false;
}
if (isFirefox) {
final RegExp firefoxRegexp = RegExp(r'Firefox\/([0-9]+)');
final RegExpMatch? match = firefoxRegexp.firstMatch(ui_web.browser.userAgent);
if (match != null) {
final int version = int.parse(match.group(1)!);
return version >= 119;
}
return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

In the past, we usually put detection logic in the engine/browser_detection.dart file and expose booleans from there.

You could, for example, introduce a isSafari174OrNewer and isFirefox119OrNewer in engine/browser_detection.dart.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great suggestion. updated.

Comment on lines 506 to 510
owner.removeAttribute('aria-description');
owner.removeAttribute('aria-describedby');
if (hint != null && hint.trim().isNotEmpty) {
_setAriaDescriptionOrDescribedBy(hint);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we avoid removing the attributes when not necessary?

Something like this might work:

Suggested change
owner.removeAttribute('aria-description');
owner.removeAttribute('aria-describedby');
if (hint != null && hint.trim().isNotEmpty) {
_setAriaDescriptionOrDescribedBy(hint);
}
if (hint != null && hint.trim().isNotEmpty) {
_setAriaDescriptionOrDescribedBy(hint);
} else {
owner.removeAttribute('aria-description');
owner.removeAttribute('aria-describedby');
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great suggestion. updated.

@flutter-zl flutter-zl enabled auto-merge May 23, 2025 18:31
@flutter-zl flutter-zl added this pull request to the merge queue May 27, 2025
Merged via the queue into flutter:master with commit 768932e May 27, 2025
178 of 179 checks passed
@flutter-zl flutter-zl deleted the hint_0520 branch May 27, 2025 16:24
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request May 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request May 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request May 27, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request May 28, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request May 28, 2025
auto-submit bot pushed a commit to flutter/packages that referenced this pull request May 28, 2025
flutter/flutter@4372bfb...0e536eb

2025-05-28 30870216+gaaclarke@users.noreply.github.com Introduces FlutterPluginRegistrant protocol. (flutter/flutter#169399)
2025-05-28 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Initialize `default-flavor` in `FlutterCommand`, adds integration test. (#169298)" (flutter/flutter#169581)
2025-05-28 matanlurey@users.noreply.github.com Initialize `default-flavor` in `FlutterCommand`, adds integration test. (flutter/flutter#169298)
2025-05-28 jakemac@google.com Update DEPS to  add dart-lang/ai repo (flutter/flutter#169540)
2025-05-28 engine-flutter-autoroll@skia.org Roll Skia from 92311f2ba0b7 to 82d326fc2148 (1 revision) (flutter/flutter#169552)
2025-05-28 kevmoo@users.noreply.github.com dev/bots: improve service worker test code (flutter/flutter#169231)
2025-05-28 magder@google.com Make Android team platform view TESTOWNERS (flutter/flutter#169297)
2025-05-28 engine-flutter-autoroll@skia.org Roll Skia from 044f58f78a73 to 92311f2ba0b7 (9 revisions) (flutter/flutter#169542)
2025-05-27 engine-flutter-autoroll@skia.org Roll Skia from 443f5257f382 to 044f58f78a73 (16 revisions) (flutter/flutter#169530)
2025-05-27 sokolovskyi.konstantin@gmail.com [web] Fix unresponsive input above SelectionArea in Safari and Firefox. (flutter/flutter#167275)
2025-05-27 58529443+srujzs@users.noreply.github.com Set pause_isolates_on_start flag if --start-paused (flutter/flutter#169392)
2025-05-27 engine-flutter-autoroll@skia.org Roll Packages from af0b9a9 to 6eebe72 (24 revisions) (flutter/flutter#169514)
2025-05-27 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from 5mpmPsuD8rpeiJizT... to nC9hLWjYVlChDTEPh... (flutter/flutter#169498)
2025-05-27 zhongliu88889@gmail.com Split hint from label and expose it via aria-description or aria-describedby (flutter/flutter#169157)
2025-05-26 github@alexv525.com 🐛 Normalize generated file paths for the l10n generator (flutter/flutter#169467)
2025-05-26 engine-flutter-autoroll@skia.org Roll Dart SDK from d811152316e4 to 6aeb798bdbe2 (2 revisions) (flutter/flutter#169478)
2025-05-26 dkwingsmt@users.noreply.github.com [Cupertino] Apply RSuperellipse to most Cupertino widgets (flutter/flutter#167784)
2025-05-26 bkonyi@google.com Roll `package:dds` to 5.0.2 (flutter/flutter#169471)
2025-05-26 matanlurey@users.noreply.github.com Use `.flutter-plugins-dependencies` for crash reporting. (flutter/flutter#169319)
2025-05-26 matanlurey@users.noreply.github.com Remove now disabled code that would generate `.flutter-plugins`. (flutter/flutter#169320)
2025-05-26 engine-flutter-autoroll@skia.org Roll Dart SDK from 7dab9bffe1f7 to d811152316e4 (1 revision) (flutter/flutter#169473)
2025-05-26 munsocket@protonmail.com Precise browser resizing with integration_test and driver (flutter/flutter#160678)
2025-05-26 matanlurey@users.noreply.github.com Add `/coverage/` to `.gitignore.tmp` (flutter/flutter#169387)
2025-05-26 matanlurey@users.noreply.github.com Make test output with encoded `dart-defines=...` human readable. (flutter/flutter#169353)
2025-05-26 matanlurey@users.noreply.github.com Use at most `PROC~/2` tasks to transform assets. (flutter/flutter#169386)
2025-05-26 32538273+ValentinVignal@users.noreply.github.com Forward exit code from dart test to flutter test (flutter/flutter#168604)
2025-05-26 51940183+Sameri11@users.noreply.github.com Fix warning when building for macOS desktop (flutter/flutter#165996)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC stuartmorgan@google.com,tarrinneal@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
FMorschel pushed a commit to FMorschel/packages that referenced this pull request Jun 9, 2025
…r#9334)

flutter/flutter@4372bfb...0e536eb

2025-05-28 30870216+gaaclarke@users.noreply.github.com Introduces FlutterPluginRegistrant protocol. (flutter/flutter#169399)
2025-05-28 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Initialize `default-flavor` in `FlutterCommand`, adds integration test. (#169298)" (flutter/flutter#169581)
2025-05-28 matanlurey@users.noreply.github.com Initialize `default-flavor` in `FlutterCommand`, adds integration test. (flutter/flutter#169298)
2025-05-28 jakemac@google.com Update DEPS to  add dart-lang/ai repo (flutter/flutter#169540)
2025-05-28 engine-flutter-autoroll@skia.org Roll Skia from 92311f2ba0b7 to 82d326fc2148 (1 revision) (flutter/flutter#169552)
2025-05-28 kevmoo@users.noreply.github.com dev/bots: improve service worker test code (flutter/flutter#169231)
2025-05-28 magder@google.com Make Android team platform view TESTOWNERS (flutter/flutter#169297)
2025-05-28 engine-flutter-autoroll@skia.org Roll Skia from 044f58f78a73 to 92311f2ba0b7 (9 revisions) (flutter/flutter#169542)
2025-05-27 engine-flutter-autoroll@skia.org Roll Skia from 443f5257f382 to 044f58f78a73 (16 revisions) (flutter/flutter#169530)
2025-05-27 sokolovskyi.konstantin@gmail.com [web] Fix unresponsive input above SelectionArea in Safari and Firefox. (flutter/flutter#167275)
2025-05-27 58529443+srujzs@users.noreply.github.com Set pause_isolates_on_start flag if --start-paused (flutter/flutter#169392)
2025-05-27 engine-flutter-autoroll@skia.org Roll Packages from af0b9a9 to 6eebe72 (24 revisions) (flutter/flutter#169514)
2025-05-27 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from 5mpmPsuD8rpeiJizT... to nC9hLWjYVlChDTEPh... (flutter/flutter#169498)
2025-05-27 zhongliu88889@gmail.com Split hint from label and expose it via aria-description or aria-describedby (flutter/flutter#169157)
2025-05-26 github@alexv525.com 🐛 Normalize generated file paths for the l10n generator (flutter/flutter#169467)
2025-05-26 engine-flutter-autoroll@skia.org Roll Dart SDK from d811152316e4 to 6aeb798bdbe2 (2 revisions) (flutter/flutter#169478)
2025-05-26 dkwingsmt@users.noreply.github.com [Cupertino] Apply RSuperellipse to most Cupertino widgets (flutter/flutter#167784)
2025-05-26 bkonyi@google.com Roll `package:dds` to 5.0.2 (flutter/flutter#169471)
2025-05-26 matanlurey@users.noreply.github.com Use `.flutter-plugins-dependencies` for crash reporting. (flutter/flutter#169319)
2025-05-26 matanlurey@users.noreply.github.com Remove now disabled code that would generate `.flutter-plugins`. (flutter/flutter#169320)
2025-05-26 engine-flutter-autoroll@skia.org Roll Dart SDK from 7dab9bffe1f7 to d811152316e4 (1 revision) (flutter/flutter#169473)
2025-05-26 munsocket@protonmail.com Precise browser resizing with integration_test and driver (flutter/flutter#160678)
2025-05-26 matanlurey@users.noreply.github.com Add `/coverage/` to `.gitignore.tmp` (flutter/flutter#169387)
2025-05-26 matanlurey@users.noreply.github.com Make test output with encoded `dart-defines=...` human readable. (flutter/flutter#169353)
2025-05-26 matanlurey@users.noreply.github.com Use at most `PROC~/2` tasks to transform assets. (flutter/flutter#169386)
2025-05-26 32538273+ValentinVignal@users.noreply.github.com Forward exit code from dart test to flutter test (flutter/flutter#168604)
2025-05-26 51940183+Sameri11@users.noreply.github.com Fix warning when building for macOS desktop (flutter/flutter#165996)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC stuartmorgan@google.com,tarrinneal@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Ortes pushed a commit to Ortes/packages that referenced this pull request Jun 25, 2025
…r#9334)

flutter/flutter@4372bfb...0e536eb

2025-05-28 30870216+gaaclarke@users.noreply.github.com Introduces FlutterPluginRegistrant protocol. (flutter/flutter#169399)
2025-05-28 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Initialize `default-flavor` in `FlutterCommand`, adds integration test. (#169298)" (flutter/flutter#169581)
2025-05-28 matanlurey@users.noreply.github.com Initialize `default-flavor` in `FlutterCommand`, adds integration test. (flutter/flutter#169298)
2025-05-28 jakemac@google.com Update DEPS to  add dart-lang/ai repo (flutter/flutter#169540)
2025-05-28 engine-flutter-autoroll@skia.org Roll Skia from 92311f2ba0b7 to 82d326fc2148 (1 revision) (flutter/flutter#169552)
2025-05-28 kevmoo@users.noreply.github.com dev/bots: improve service worker test code (flutter/flutter#169231)
2025-05-28 magder@google.com Make Android team platform view TESTOWNERS (flutter/flutter#169297)
2025-05-28 engine-flutter-autoroll@skia.org Roll Skia from 044f58f78a73 to 92311f2ba0b7 (9 revisions) (flutter/flutter#169542)
2025-05-27 engine-flutter-autoroll@skia.org Roll Skia from 443f5257f382 to 044f58f78a73 (16 revisions) (flutter/flutter#169530)
2025-05-27 sokolovskyi.konstantin@gmail.com [web] Fix unresponsive input above SelectionArea in Safari and Firefox. (flutter/flutter#167275)
2025-05-27 58529443+srujzs@users.noreply.github.com Set pause_isolates_on_start flag if --start-paused (flutter/flutter#169392)
2025-05-27 engine-flutter-autoroll@skia.org Roll Packages from af0b9a9 to 6eebe72 (24 revisions) (flutter/flutter#169514)
2025-05-27 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from 5mpmPsuD8rpeiJizT... to nC9hLWjYVlChDTEPh... (flutter/flutter#169498)
2025-05-27 zhongliu88889@gmail.com Split hint from label and expose it via aria-description or aria-describedby (flutter/flutter#169157)
2025-05-26 github@alexv525.com 🐛 Normalize generated file paths for the l10n generator (flutter/flutter#169467)
2025-05-26 engine-flutter-autoroll@skia.org Roll Dart SDK from d811152316e4 to 6aeb798bdbe2 (2 revisions) (flutter/flutter#169478)
2025-05-26 dkwingsmt@users.noreply.github.com [Cupertino] Apply RSuperellipse to most Cupertino widgets (flutter/flutter#167784)
2025-05-26 bkonyi@google.com Roll `package:dds` to 5.0.2 (flutter/flutter#169471)
2025-05-26 matanlurey@users.noreply.github.com Use `.flutter-plugins-dependencies` for crash reporting. (flutter/flutter#169319)
2025-05-26 matanlurey@users.noreply.github.com Remove now disabled code that would generate `.flutter-plugins`. (flutter/flutter#169320)
2025-05-26 engine-flutter-autoroll@skia.org Roll Dart SDK from 7dab9bffe1f7 to d811152316e4 (1 revision) (flutter/flutter#169473)
2025-05-26 munsocket@protonmail.com Precise browser resizing with integration_test and driver (flutter/flutter#160678)
2025-05-26 matanlurey@users.noreply.github.com Add `/coverage/` to `.gitignore.tmp` (flutter/flutter#169387)
2025-05-26 matanlurey@users.noreply.github.com Make test output with encoded `dart-defines=...` human readable. (flutter/flutter#169353)
2025-05-26 matanlurey@users.noreply.github.com Use at most `PROC~/2` tasks to transform assets. (flutter/flutter#169386)
2025-05-26 32538273+ValentinVignal@users.noreply.github.com Forward exit code from dart test to flutter test (flutter/flutter#168604)
2025-05-26 51940183+Sameri11@users.noreply.github.com Fix warning when building for macOS desktop (flutter/flutter#165996)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC stuartmorgan@google.com,tarrinneal@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 14, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 14, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 15, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 15, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 16, 2025
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) engine flutter/engine related. See also e: labels. platform-web Web applications specifically
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants