Skip to content

[web] Omit group role on menu scrollables and assign region role to named routes - #192965

Merged
auto-submit[bot] merged 7 commits into
flutter:masterfrom
kevmoo:web-a11y-pr3-menu-route-roles
Sep 25, 2026
Merged

auto-submit[bot] merged 7 commits into
flutter:masterfrom
kevmoo:web-a11y-pr3-menu-route-roles

Conversation

@kevmoo

@kevmoo kevmoo commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Description

Fixes two web accessibility ARIA role issues in lib/web_ui:

  1. VoiceOver item count off-by-one in PopupMenuButton, MenuAnchor, and DropdownButton (#190661): When a menu renders on the web, its items are wrapped in an intermediate SingleChildScrollView (SemanticScrollable, which sets role="group" in its constructor) while SemanticMenu / SemanticMenuBar sets aria-owns directly to the descendant menuItem nodes. Leaving role="group" on the unlabeled intermediate scrollable causes WebKit/VoiceOver to count the scrollable container as child 1 alongside the N aria-owns menuitems ("menu, N+1 items", announcing item 1 as "2 of N+1"). Setting role="none" on unlabeled intermediate SemanticScrollable containers inside SemanticMenu and SemanticMenuBar eliminates the off-by-one item count while preserving DOM scroll behavior.
  2. Valid ARIA role for self-labeled SemanticRoute (aria-prohibited-attr, http://b/462008723, http://b/461982793, http://b/460641753): When SemanticRoute has namesRoute: true (setting aria-label on <flt-semantics>), it previously omitted the role attribute (role="generic"), violating WAI-ARIA 1.2 aria-prohibited-attr (aria-label is prohibited on generic elements). SemanticRoute.update() now sets role="region" when semanticsObject.namesRoute is true.

Fixes #190661
Fixes http://b/462008723
Fixes http://b/461982793
Fixes http://b/460641753

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.

…le to named routes

When PopupMenuButton, MenuAnchor, or DropdownButton renders a menu on the web, its items are wrapped in an intermediate SingleChildScrollView (SemanticScrollable with role='group') while SemanticMenu sets aria-owns directly to the descendant menuItem nodes. Leaving role='group' on the unlabeled intermediate scrollable causes screen readers (such as VoiceOver on macOS) to count the scrollable container as an extra first item ('menu, N+1 items', announcing item 1 as '2 of N+1'). Setting role='none' on unlabeled intermediate SemanticScrollable containers inside SemanticMenu and SemanticMenuBar eliminates the off-by-one item count while preserving DOM scroll behavior.

Additionally, when a SemanticRoute sets namesRoute: true (applying aria-label to <flt-semantics>), set role='region' so self-labeled routes do not violate WAI-ARIA aria-prohibited-attr on generic elements.

Fixes flutter#190661
@github-actions github-actions Bot added engine flutter/engine related. See also e: labels. a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) f: routes Navigator, Router, and related APIs. platform-web Web applications specifically team-web Owned by Web platform team labels Sep 18, 2026
@kevmoo kevmoo changed the title fix(web_ui): omit group role on menu scrollables and assign region role to named routes [web] Omit group role on menu scrollables and assign region role to named routes Sep 18, 2026
@github-actions github-actions Bot added the f: scrolling Viewports, list views, slivers, etc. label Sep 18, 2026
@kevmoo
kevmoo marked this pull request as ready for review September 18, 2026 04:06

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors menu semantics by introducing a shared _SemanticMenuContainerBase class, updates SemanticRoute to set its ARIA role to 'region' when it has a route name and label, and configures SemanticScrollable to set its ARIA role to 'none' when unlabeled and nested within a menu. The review feedback highlights a redundant check in menus.dart that duplicates the role-setting logic already handled by SemanticScrollable, recommending its removal to maintain a single source of truth.

Comment thread engine/src/flutter/lib/web_ui/lib/src/engine/semantics/menus.dart Outdated
@flutter-zl
flutter-zl self-requested a review September 23, 2026 17:23
@flutter-zl flutter-zl added the CICD Run CI/CD label Sep 23, 2026
@flutter-dashboard

Copy link
Copy Markdown

CI had a failure that stopped further tests from running. We need to investigate to determine the root cause.

SHA at time of execution: 88ec987.

Possible causes:

  • Configuration Changes: The .ci.yaml file might have been modified between the creation of this pull request and the start of this test run. This can lead to ci yaml validation errors.
  • Infrastructure Issues: Problems with the CI environment itself (e.g., quota) could have caused the failure.

A blank commit, or merging to head, will be required to resume running CI for this PR.

Error Details:

FormatException: ERROR: Mac_arm64_ios basic_material_app_ios__compile is a new builder added. it needs to be marked bringup: true
If ci.yaml wasn't changed, try `git fetch upstream && git merge upstream/master`



Stack trace:

#0      CiYaml._validate (package:cocoon_service/src/model/ci_yaml/ci_yaml.dart:395:7)
#1      new CiYaml (package:cocoon_service/src/model/ci_yaml/ci_yaml.dart:128:7)
#2      new CiYamlSet (package:cocoon_service/src/model/ci_yaml/ci_yaml.dart:48:23)
#3      CiYamlFetcher._getCiYaml (package:cocoon_service/src/service/scheduler/ci_yaml_fetcher.dart:124:12)
<asynchronous suspension>
#4      Scheduler.getPresubmitTargets (package:cocoon_service/src/service/scheduler.dart:1064:20)
<asynchronous suspension>
#5      Scheduler._getTestsForStage (package:cocoon_service/src/service/scheduler.dart:1367:14)
<asynchronous suspension>
#6      Scheduler._runCiTestingStage (package:cocoon_service/src/service/scheduler.dart:1408:30)
<asynchronous suspension>
#7      Scheduler.proceedToCiTestingStage (package:cocoon_service/src/service/scheduler.dart:1517:7)
<asynchronous suspension>
#8      Scheduler._closeSuccessfulEngineBuildStage (package:cocoon_service/src/service/scheduler.dart:1312:5)
<asynchronous suspension>
#9      Scheduler.processCheckRunCompleted (package:cocoon_service/src/service/scheduler.dart:1253:11)
<asynchronous suspension>
#10     PresubmitSubscription._processBuild (package:cocoon_service/src/request_handlers/presubmit_subscription.dart:228:7)
<asynchronous suspension>
#11     PresubmitSubscription.post (package:cocoon_service/src/request_handlers/presubmit_subscription.dart:119:5)
<asynchronous suspension>
#12     RequestHandler.service (package:cocoon_service/src/request_handling/request_handler.dart:42:20)
<asynchronous suspension>
#13     SubscriptionHandler.service (package:cocoon_service/src/request_handling/subscription_handler.dart:134:5)
<asynchronous suspension>
#14     createServer.<anonymous closure> (package:cocoon_service/server.dart:462:7)
<asynchronous suspension>
#15     main.<anonymous closure>.<anonymous closure> (file:///app/app_dart/bin/gae_server.dart:192:9)
<asynchronous suspension>

@flutter-zl flutter-zl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I deployed both before and after change app to firebase. DropdownButton still announces N+1 items.

Before: https://flutter-demo-65-before.web.app, After: https://flutter-demo-65-after.web.app

Image Image Image Image

The dropdown menu is a ListView, which the framework splits into two semantics nodes: an outer node with no config and an inner scrollable node. The PR only sets role="none" on the inner one, so the outer node shows up as a generic element and is still the menu's first child. PopupMenuButton is fixed because SingleChildScrollView produces only one node.

kevmoo added a commit to kevmoo/scripts.dart that referenced this pull request Sep 24, 2026
…d Changes Requested (Resolved) (#124)

- Copy `reviewAuthors`, `approvedReviewers`, `lastAuthorReviewAt`,
`lastCommitAt`, and `lastUnrequestedReviewAt` when `_attachLocalStatus`
constructs the enriched `GhPr` instance in `runGhView`.
- Require `hasAuthorRespondedSinceLastReview` before classifying
`CHANGES_REQUESTED` with `areThreadsResolved` as `Changes Requested
(Resolved)` / `Re-review Needed (threads resolved)`, so top-level
`CHANGES_REQUESTED` reviews on PRs with older resolved bot threads (e.g.
`flutter/flutter#192965`) remain `🔴 Changes Requested (@Reviewer)`.
- Set `maxPageSize: 15` in `fetchOpenPullRequests` to avoid GitHub
GraphQL `HTTP 502` gateway timeouts on dense PR pages.
When DropdownButton renders a ListView inside a menu, the framework splits the ListView into an outer unlabeled GenericRole container node and an inner SemanticScrollable node. Setting role='none' on unlabeled, non-tappable GenericRole nodes with a menu/menuBar ancestor prevents the browser from exposing the outer wrapper as an extra generic child of role='menu'.
@kevmoo

kevmoo commented Sep 24, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for testing and catching that on DropdownButton, @flutter-zl!

You nailed the root cause: DropdownButton uses a ListView inside Semantics(role: SemanticsRole.menu), which splits into an outer unlabeled, non-tappable GenericRole container node and an inner SemanticScrollable node. Because <flt-semantics> has explicit layout styling and is a direct DOM child of <flt-semantics role="menu" aria-owns="...">, leaving role unset on the outer GenericRole node caused the browser to include it as a generic DOM child ahead of the aria-owns menuitem children (1 + N items).

Updated GenericRole.update() in semantics.dart (sharing hasMenuAncestor with SemanticScrollable) so unlabeled, non-tappable GenericRole wrapper nodes inside a menu or menuBar also receive role="none", and added a 2-node (GenericRole + SemanticScrollable) regression test in semantics_test.dart. PTAL!

@kevmoo
kevmoo requested a review from flutter-zl September 24, 2026 17:08

@flutter-zl flutter-zl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Great. I tested with https://flutter-demo-65-after.web.app/. it works. Thanks for fixing this issue.

Before: https://flutter-demo-65-before.web.app, After: https://flutter-demo-65-after.web.app

@flutter-zl flutter-zl added the autosubmit Merge PR when tree becomes green via auto submit App label Sep 25, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Sep 25, 2026
Merged via the queue into flutter:master with commit e20e5c3 Sep 25, 2026
24 of 25 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Sep 25, 2026
@kevmoo
kevmoo deleted the web-a11y-pr3-menu-route-roles branch September 25, 2026 19:09
DanTup pushed a commit to DanTup/flutter that referenced this pull request Sep 28, 2026
…amed routes (flutter#192965)

## Description

Fixes two web accessibility ARIA role issues in `lib/web_ui`:

1. **VoiceOver item count off-by-one in `PopupMenuButton`, `MenuAnchor`,
and `DropdownButton` (`flutter#190661`)**: When a menu renders on the web, its
items are wrapped in an intermediate `SingleChildScrollView`
(`SemanticScrollable`, which sets `role="group"` in its constructor)
while `SemanticMenu` / `SemanticMenuBar` sets `aria-owns` directly to
the descendant `menuItem` nodes. Leaving `role="group"` on the unlabeled
intermediate scrollable causes WebKit/VoiceOver to count the scrollable
container as child `1` alongside the `N` `aria-owns` `menuitem`s
(`"menu, N+1 items"`, announcing item 1 as `"2 of N+1"`). Setting
`role="none"` on unlabeled intermediate `SemanticScrollable` containers
inside `SemanticMenu` and `SemanticMenuBar` eliminates the off-by-one
item count while preserving DOM scroll behavior.
2. **Valid ARIA role for self-labeled `SemanticRoute`
(`aria-prohibited-attr`, http://b/462008723, http://b/461982793,
http://b/460641753)**: When `SemanticRoute` has `namesRoute: true`
(setting `aria-label` on `<flt-semantics>`), it previously omitted the
`role` attribute (`role="generic"`), violating WAI-ARIA 1.2
`aria-prohibited-attr` (`aria-label` is prohibited on generic elements).
`SemanticRoute.update()` now sets `role="region"` when
`semanticsObject.namesRoute` is true.

Fixes flutter#190661
Fixes http://b/462008723
Fixes http://b/461982793
Fixes http://b/460641753

## Pre-launch Checklist

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

## Description

Fixes two web accessibility ARIA role issues in `lib/web_ui`:

1. **VoiceOver item count off-by-one in `PopupMenuButton`, `MenuAnchor`,
and `DropdownButton` (`flutter#190661`)**: When a menu renders on the web, its
items are wrapped in an intermediate `SingleChildScrollView`
(`SemanticScrollable`, which sets `role="group"` in its constructor)
while `SemanticMenu` / `SemanticMenuBar` sets `aria-owns` directly to
the descendant `menuItem` nodes. Leaving `role="group"` on the unlabeled
intermediate scrollable causes WebKit/VoiceOver to count the scrollable
container as child `1` alongside the `N` `aria-owns` `menuitem`s
(`"menu, N+1 items"`, announcing item 1 as `"2 of N+1"`). Setting
`role="none"` on unlabeled intermediate `SemanticScrollable` containers
inside `SemanticMenu` and `SemanticMenuBar` eliminates the off-by-one
item count while preserving DOM scroll behavior.
2. **Valid ARIA role for self-labeled `SemanticRoute`
(`aria-prohibited-attr`, http://b/462008723, http://b/461982793,
http://b/460641753)**: When `SemanticRoute` has `namesRoute: true`
(setting `aria-label` on `<flt-semantics>`), it previously omitted the
`role` attribute (`role="generic"`), violating WAI-ARIA 1.2
`aria-prohibited-attr` (`aria-label` is prohibited on generic elements).
`SemanticRoute.update()` now sets `role="region"` when
`semanticsObject.namesRoute` is true.

Fixes flutter#190661
Fixes http://b/462008723
Fixes http://b/461982793
Fixes http://b/460641753

## Pre-launch Checklist

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

Manual roll Flutter from 4fcd90be0045 to c5a061b18fa2 (222 revisions)

Manual roll requested by [email protected]

flutter/flutter@4fcd90b...c5a061b

2026-09-28 [email protected] [cross imports] navigator_test.dart (flutter/flutter#190954)
2026-09-28 [email protected] Revert: Reland: Only render views that need to be rendered  (flutter/flutter#193360)
2026-09-28 [email protected] ci(bringup): android_intent_security_test is green (flutter/flutter#193469)
2026-09-28 [email protected] Sync CHANGELOG.md from stable (flutter/flutter#193020)
2026-09-28 [email protected] Roll Fuchsia Linux SDK from ukukV5lEkKabtOATk... to QdgqP02_cYpRQQQNN... (flutter/flutter#193454)
2026-09-28 [email protected] Roll pub packages (flutter/flutter#193460)
2026-09-28 [email protected] Un-nest sceneBuildDuration and windowRenderDuration in web SceneBuilderRecorder (flutter/flutter#193260)
2026-09-28 [email protected] Fix unresolved doc comment references in dev/integration_tests and dev/benchmarks (flutter/flutter#193433)
2026-09-28 [email protected] Roll Packages from e55e7ac to ba0364a (9 revisions) (flutter/flutter#193450)
2026-09-28 [email protected] [tool] Require explicit dependency injection for FlutterDevice and FlutterDevice.create (flutter/flutter#192830)
2026-09-28 [email protected] Roll pub packages (flutter/flutter#193442)
2026-09-28 [email protected] [flutter_tools] Include data assets from hooks when pubspec.yaml is empty (flutter/flutter#193434)
2026-09-28 [email protected] Roll pub packages (flutter/flutter#193438)
2026-09-28 [email protected] Fix unresolved doc comment references in Material and Cupertino (flutter/flutter#193283)
2026-09-28 [email protected] Fix doc references to Material and Cupertino in the widgets library and its tests (flutter/flutter#193334)
2026-09-26 [email protected] wasm: enforce WasmGC opt-in capability checks and Firefox < 147 guard (flutter/flutter#193180)
2026-09-26 [email protected] [Android] Refuse external setters of engine entrypoint and cached engine arguments via `Intent`s (flutter/flutter#190249)
2026-09-26 [email protected] [web] Unskip TextPainter.getWordBoundary test (flutter/flutter#193378)
2026-09-26 [email protected] Specify non-obvious types in pattern variable declarations (flutter/flutter#192632)
2026-09-26 [email protected] Removes feedback loop from advanced filters without offscreen msaa and framebufferfetch (flutter/flutter#193306)
2026-09-26 [email protected] [web] Unskip 8 passing image tests in painting and rendering (flutter/flutter#193377)
2026-09-26 [email protected] Revert: Allow resetting test invariants in `addTearDown` (flutter/flutter#193383)
2026-09-25 [email protected] [tool] Migrate CoverageCollector to modular dependency injection (flutter/flutter#192924)
2026-09-25 [email protected] Fix more unresolved doc comment references (flutter/flutter#193336)
2026-09-25 [email protected] Allow resetting test invariants in `addTearDown` (flutter/flutter#192082)
2026-09-25 [email protected] [a11y] Add a semantics role for slider (flutter/flutter#193324)
2026-09-25 [email protected] Fix plugin tests after Pigeon plugin template changes (flutter/flutter#193302)
2026-09-25 [email protected] [Android 17] Bump Standard Test Apps in flutter/flutter to AGP 9.3.1 (flutter/flutter#193263)
2026-09-25 [email protected] SSBO-based gradients in UberSDF (flutter/flutter#192962)
2026-09-25 [email protected] [Linux] Handle FlView being destroyed before rendering is complete. (flutter/flutter#193268)
2026-09-25 [email protected] [flutter_tools] Handle Windows reserved characters in test target path (flutter/flutter#191900)
2026-09-25 [email protected] docs(tools): nit: say hook/build.dart in CMake native assets comment (flutter/flutter#192205)
2026-09-25 [email protected] [web] Enable Firefox Skwasm UI CI suites, configure COI configs, and fail fast on loader rejections (flutter/flutter#193187)
2026-09-25 [email protected] [Android 17] Bumped Engine Dependencies to 9.3.1 (flutter/flutter#193265)
2026-09-25 [email protected] [web] Omit group role on menu scrollables and assign region role to named routes (flutter/flutter#192965)
2026-09-25 [email protected] Roll Skia from 8eedeed98e79 to f441ca223b2b (5 revisions) (flutter/flutter#193353)
2026-09-25 [email protected] Roll Fuchsia Linux SDK from EbPpoJW-Lnsu-8dyZ... to ukukV5lEkKabtOATk... (flutter/flutter#193326)
2026-09-25 [email protected] Roll Packages from 431ea69 to e55e7ac (6 revisions) (flutter/flutter#193351)
2026-09-25 [email protected] Support DynamicLibrary.codeAsset in Flutter (flutter/flutter#188947)
2026-09-25 [email protected] Roll Dart SDK from 75d9e87d4e3d to 0e7642b85457 (1 revision) (flutter/flutter#193346)
2026-09-25 [email protected] Exclude iOS PRs from macOS link (flutter/flutter#193315)
2026-09-25 [email protected] Roll Skia from 6357608543ec to 8eedeed98e79 (4 revisions) (flutter/flutter#193341)
2026-09-25 [email protected] Roll Dart SDK from d3aedb186aab to 75d9e87d4e3d (3 revisions) (flutter/flutter#193335)
2026-09-25 [email protected] Cache the paint offset adjusted line metrics in TextPainter (flutter/flutter#191223)
...
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) CICD Run CI/CD engine flutter/engine related. See also e: labels. f: routes Navigator, Router, and related APIs. f: scrolling Viewports, list views, slivers, etc. platform-web Web applications specifically team-web Owned by Web platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[web] VoiceOver announces menu items with an off-by-one position/count (N-item menu reads as "2 of N+1")

2 participants