-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Fix Voiceover traversal for OutlinedButton.icon #175810
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
chunhtai
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the intention of this pr to preserve the renderobject so that it can have stable id when switching? If so I think this approach is fine.
| buttonStyle?.iconAlignment ?? | ||
| IconAlignment.start; | ||
| final Widget? icon = this.icon; | ||
| return Row( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you still need a row if there is no icon?
chunhtai
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
| await tester.tap(find.text('OUTLINED')); | ||
| await tester.pumpAndSettle(); | ||
| expect(find.byType(OutlinedButton).evaluate().length, 2); | ||
| expect(find.byType(OutlinedButton).evaluate().length, 4); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed this test as originally OutlinedButton.icon was returning _OutlinedButtonWithIcon, and didnt count as an OutlinedButton.
chunhtai
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you can't figure out a good way to fix this, you can use a MergeSemantics on top of the button subtree to force everything into a single node, and as long as the MergeSemantics doesn't change, it should persist the semanticsnode
| ); | ||
|
|
||
| // Only apply paddings when OutlinedButton has an Icon | ||
| if (child is _OutlinedButtonWithIconChild && theme.useMaterial3) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should avoid runtimeType check like this. This compromised the composition nature of Flutter. Can the padding be applied in the _OutlinedButtonWithIconChild instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so? _OutlinedButtonWithIconChild doesnt have a method defaultStyleOf. The styling here ^ also requires context so I can't easily move it to the child
Sorry, can you elaborate what you mean? My guess was that I apply MergeSemantics around the entire check for OutlinedButton.icon , like But OutlinedButton.icon requires a type of OutlinedButton to be returned |
yeah you are right, you could change the factory constructor to a static method to get around it though. How bad is the g3 migration for the previous fix? Another suggestion. Convert the factory into a regular constructor, and add a private property |
flutter/flutter@5c0c9e9...908012d 2025-10-05 [email protected] Roll Skia from 5479115ef5bf to 1fd0ca1f2120 (1 revision) (flutter/flutter#176541) 2025-10-05 [email protected] Roll Fuchsia Linux SDK from oWcBvgdpdlGvaqiDg... to Zm6K_3gP3VCaMy9rH... (flutter/flutter#176538) 2025-10-05 [email protected] Roll Dart SDK from 53aeaeb2454c to 016a8c0045fd (1 revision) (flutter/flutter#176531) 2025-10-04 [email protected] Roll Skia from f316de3d47b4 to 5479115ef5bf (4 revisions) (flutter/flutter#176529) 2025-10-04 [email protected] Roll Dart SDK from 9bc52df78b67 to 53aeaeb2454c (1 revision) (flutter/flutter#176525) 2025-10-04 [email protected] Roll Fuchsia Linux SDK from HUhTcRn-LUXa2Salu... to oWcBvgdpdlGvaqiDg... (flutter/flutter#176515) 2025-10-04 [email protected] Fix TextFormField does not inherit local InputDecorationTheme (flutter/flutter#176397) 2025-10-04 [email protected] Roll Dart SDK from 0009748aed50 to 9bc52df78b67 (4 revisions) (flutter/flutter#176506) 2025-10-04 [email protected] Roll Skia from 9cda1a2050c4 to f316de3d47b4 (2 revisions) (flutter/flutter#176504) 2025-10-04 [email protected] fix: support older git (ubuntu 22.04) in content hash (flutter/flutter#176321) 2025-10-04 [email protected] Roll Skia from a454242c3934 to 9cda1a2050c4 (2 revisions) (flutter/flutter#176499) 2025-10-03 [email protected] [material/menu_anchor.dart] Check for reserved padding updates on layout delegate. (flutter/flutter#176457) 2025-10-03 [email protected] Roll Skia from b842026480e0 to a454242c3934 (3 revisions) (flutter/flutter#176484) 2025-10-03 [email protected] Starts updating the DEPS in preupload. (flutter/flutter#176485) 2025-10-03 [email protected] Align flutter dependencies with ones coming from dart. (flutter/flutter#176475) 2025-10-03 [email protected] fix: delay exiting microbenchmark (flutter/flutter#176477) 2025-10-03 [email protected] Add state restoration for UIScene migration (flutter/flutter#176305) 2025-10-03 [email protected] Fix Voiceover traversal for OutlinedButton.icon (flutter/flutter#175810) 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 [email protected] 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
Fixes flutter#172489 When an OutlinedButton.icon is initially built with a null icon and later updated to display an icon, the underlying widget implementation changes. The button switches from a standard OutlinedButton to a private _OutlinedButtonWithIcon widget. This switch causes the button's entire semantic node to be destroyed and a new one created in its place. For screen readers like VoiceOver, this behavior is disruptive. If an accessibility service is focused on the button when its icon appears, that focus is lost because the original semantic node is discarded, leading to strange behaviour as it makes an best effort to focus on an existing node. This PR resolves the issue by ensuring the same widget is used regardless of whether the icon is present. The logic for _OutlinedButtonWithIcon has been merged into the base class OutlinedButton, ensuring that no matter if you call OutlinedButton.icon or OutlinedButton you recieve a widget of type OutlinedButton. Demo: https://github.com/user-attachments/assets/e012bac9-823e-46f1-8eba-ec70e6b260a1
Fixes flutter#172489 When an OutlinedButton.icon is initially built with a null icon and later updated to display an icon, the underlying widget implementation changes. The button switches from a standard OutlinedButton to a private _OutlinedButtonWithIcon widget. This switch causes the button's entire semantic node to be destroyed and a new one created in its place. For screen readers like VoiceOver, this behavior is disruptive. If an accessibility service is focused on the button when its icon appears, that focus is lost because the original semantic node is discarded, leading to strange behaviour as it makes an best effort to focus on an existing node. This PR resolves the issue by ensuring the same widget is used regardless of whether the icon is present. The logic for _OutlinedButtonWithIcon has been merged into the base class OutlinedButton, ensuring that no matter if you call OutlinedButton.icon or OutlinedButton you recieve a widget of type OutlinedButton. Demo: https://github.com/user-attachments/assets/e012bac9-823e-46f1-8eba-ec70e6b260a1
## Description This PR cleanup `OutlinedButton.icon` documentation and recent logic change from #175810.
…ng icon (#176579) ## Description This PR changes `TextButton.icon` to avoid building a different widget. When a different widget is created the whole subtree is recreated which leads to various issues (Focus and A11y issues for instance). The change is similar to #175810 which fixed the exact same problem for `OutlinedButton.icon`. ## Related Issue Fixes [TextButton.icon breaks focus traversal and ink effect when toggling icon](#173944) ## Tests - Adds 1 test --------- Co-authored-by: chunhtai <[email protected]>
…ng icon (flutter#176579) ## Description This PR changes `TextButton.icon` to avoid building a different widget. When a different widget is created the whole subtree is recreated which leads to various issues (Focus and A11y issues for instance). The change is similar to flutter#175810 which fixed the exact same problem for `OutlinedButton.icon`. ## Related Issue Fixes [TextButton.icon breaks focus traversal and ink effect when toggling icon](flutter#173944) ## Tests - Adds 1 test --------- Co-authored-by: chunhtai <[email protected]>
…l and VoiceOver (#177593) ## Description This PR changes `FilledButton.icon` and `FilledButton.tonalIcon` to avoid building a different widget. When a different widget is created the whole subtree is recreated which leads to various issues (Focus and A11y issues for instance). The change is similar to #175810 which fixed the exact same problem for `OutlinedButton.icon`. ## Related Issue [TextButton.icon breaks focus traversal and ink effect when toggling icon](#173944) [Voiceover focus traversal breaks if a button's state changes to include an icon](#175810) ## Tests - Adds 4 tests
…gling icon (#177579) ## Description This PR changes `ElevatedButton.icon` to avoid building a different widget. When a different widget is created the whole subtree is recreated which leads to various issues (Focus and A11y issues for instance). The change is similar to #175810 which fixed the exact same problem for `OutlinedButton.icon`. ## Related Issue [TextButton.icon breaks focus traversal and ink effect when toggling icon](#173944) [Voiceover focus traversal breaks if a button's state changes to include an icon](#175810) ## Tests - Adds 2 tests
Fixes flutter#172489 When an OutlinedButton.icon is initially built with a null icon and later updated to display an icon, the underlying widget implementation changes. The button switches from a standard OutlinedButton to a private _OutlinedButtonWithIcon widget. This switch causes the button's entire semantic node to be destroyed and a new one created in its place. For screen readers like VoiceOver, this behavior is disruptive. If an accessibility service is focused on the button when its icon appears, that focus is lost because the original semantic node is discarded, leading to strange behaviour as it makes an best effort to focus on an existing node. This PR resolves the issue by ensuring the same widget is used regardless of whether the icon is present. The logic for _OutlinedButtonWithIcon has been merged into the base class OutlinedButton, ensuring that no matter if you call OutlinedButton.icon or OutlinedButton you recieve a widget of type OutlinedButton. Demo: https://github.com/user-attachments/assets/e012bac9-823e-46f1-8eba-ec70e6b260a1
…#176630) ## Description This PR cleanup `OutlinedButton.icon` documentation and recent logic change from flutter#175810.
…ng icon (flutter#176579) ## Description This PR changes `TextButton.icon` to avoid building a different widget. When a different widget is created the whole subtree is recreated which leads to various issues (Focus and A11y issues for instance). The change is similar to flutter#175810 which fixed the exact same problem for `OutlinedButton.icon`. ## Related Issue Fixes [TextButton.icon breaks focus traversal and ink effect when toggling icon](flutter#173944) ## Tests - Adds 1 test --------- Co-authored-by: chunhtai <[email protected]>
…l and VoiceOver (flutter#177593) ## Description This PR changes `FilledButton.icon` and `FilledButton.tonalIcon` to avoid building a different widget. When a different widget is created the whole subtree is recreated which leads to various issues (Focus and A11y issues for instance). The change is similar to flutter#175810 which fixed the exact same problem for `OutlinedButton.icon`. ## Related Issue [TextButton.icon breaks focus traversal and ink effect when toggling icon](flutter#173944) [Voiceover focus traversal breaks if a button's state changes to include an icon](flutter#175810) ## Tests - Adds 4 tests
…gling icon (flutter#177579) ## Description This PR changes `ElevatedButton.icon` to avoid building a different widget. When a different widget is created the whole subtree is recreated which leads to various issues (Focus and A11y issues for instance). The change is similar to flutter#175810 which fixed the exact same problem for `OutlinedButton.icon`. ## Related Issue [TextButton.icon breaks focus traversal and ink effect when toggling icon](flutter#173944) [Voiceover focus traversal breaks if a button's state changes to include an icon](flutter#175810) ## Tests - Adds 2 tests
Fixes #172489
When an OutlinedButton.icon is initially built with a null icon and later updated to display an icon, the underlying widget implementation changes. The button switches from a standard OutlinedButton to a private _OutlinedButtonWithIcon widget.
This switch causes the button's entire semantic node to be destroyed and a new one created in its place. For screen readers like VoiceOver, this behavior is disruptive. If an accessibility service is focused on the button when its icon appears, that focus is lost because the original semantic node is discarded, leading to strange behaviour as it makes an best effort to focus on an existing node.
This PR resolves the issue by ensuring the same widget is used regardless of whether the icon is present. The logic for _OutlinedButtonWithIcon has been merged into the base class OutlinedButton, ensuring that no matter if you call OutlinedButton.icon or OutlinedButton you recieve a widget of type OutlinedButton.
Demo:
Video_20250924_112414_759.mp4