Skip to content

Conversation

@LouiseHsu
Copy link
Contributor

@LouiseHsu LouiseHsu commented Sep 22, 2025

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

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. labels Sep 22, 2025
@chunhtai chunhtai self-requested a review September 22, 2025 21:42
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.

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(
Copy link
Contributor

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?

@LouiseHsu LouiseHsu changed the title DRAFT - Voice fix voiceover traversal for outlinedButton.icon Fix Voiceover traversal for OutlinedButton.icon Sep 24, 2025
@LouiseHsu LouiseHsu marked this pull request as ready for review September 24, 2025 19:31
@LouiseHsu LouiseHsu requested a review from chunhtai September 24, 2025 20:57
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

await tester.tap(find.text('OUTLINED'));
await tester.pumpAndSettle();
expect(find.byType(OutlinedButton).evaluate().length, 2);
expect(find.byType(OutlinedButton).evaluate().length, 4);
Copy link
Contributor Author

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.

@LouiseHsu LouiseHsu requested a review from chunhtai October 1, 2025 18:38
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.

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) {
Copy link
Contributor

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?

Copy link
Contributor Author

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

@LouiseHsu
Copy link
Contributor Author

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

Sorry, can you elaborate what you mean? My guess was that I apply MergeSemantics around the entire check for OutlinedButton.icon , like

return MergeSemantics(
    child: icon ? OutlinedButton(
         ...
      ) : _OutlinedButtonWithIcon(
        icon : icon
        ...
      ),
    );

But OutlinedButton.icon requires a type of OutlinedButton to be returned

@chunhtai
Copy link
Contributor

chunhtai commented Oct 1, 2025

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 _getPadding to the class(so that it is not exposed), and then the .icon constructor can set this callback to get padding if icon is not null, default constructor will just set this to a static method that always return null so that it can still be const

Merged via the queue into flutter:master with commit 22a2203 Oct 3, 2025
156 checks passed
@flutter-dashboard flutter-dashboard bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Oct 3, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 4, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 4, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 5, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 5, 2025
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Oct 6, 2025
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
mboetger pushed a commit to mboetger/flutter that referenced this pull request Oct 7, 2025
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
okorohelijah pushed a commit to okorohelijah/flutter that referenced this pull request Oct 7, 2025
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
fluttermirroringbot pushed a commit that referenced this pull request Oct 10, 2025
## Description

This PR cleanup `OutlinedButton.icon` documentation and recent logic
change from #175810.
github-merge-queue bot pushed a commit that referenced this pull request Oct 28, 2025
…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]>
walley892 pushed a commit to walley892/flutter that referenced this pull request Oct 30, 2025
…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]>
github-merge-queue bot pushed a commit that referenced this pull request Oct 31, 2025
…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
github-merge-queue bot pushed a commit that referenced this pull request Oct 31, 2025
…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
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Nov 12, 2025
reidbaker pushed a commit to AbdeMohlbi/flutter that referenced this pull request Dec 10, 2025
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
reidbaker pushed a commit to AbdeMohlbi/flutter that referenced this pull request Dec 10, 2025
…#176630)

## Description

This PR cleanup `OutlinedButton.icon` documentation and recent logic
change from flutter#175810.
reidbaker pushed a commit to AbdeMohlbi/flutter that referenced this pull request Dec 10, 2025
…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]>
reidbaker pushed a commit to AbdeMohlbi/flutter that referenced this pull request Dec 10, 2025
…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
reidbaker pushed a commit to AbdeMohlbi/flutter that referenced this pull request Dec 10, 2025
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Google3 Bug][A11Y]: Voiceover focus traversal breaks if a button's state changes to include an icon

2 participants