Skip to content

Conversation

@huycozy
Copy link
Member

@huycozy huycozy commented Sep 24, 2025

Issue

Description

This PR proposes a fix by adding a hint to Semantics here:

return Semantics(
inMutuallyExclusiveGroup: true,
checked: value,
selected: accessibilitySelected,
hint: semanticsHint,
child: buildToggleableWithChild(

The hint is only available on iOS &macOS platforms with a localized string for unselected state to prevent regression on Android, which works as expected currently and this seems to be the only solution as investigated below ⬇️

Demo the fix
Flutter.ios.-.fixed.mov

Why can't we make it simpler with Semantics flags only?

➡️ Not sure which flags can trigger unselected state implicitly.

How do iOS native apps work?

I did test with two approaches:

  1. iOS Reminders app:
    VoiceOver vocalizes as follows:
    Unchecked radio: "...Incomplete..."
    checked radio:"...Completed..."
Demo video
iOS.Reminders.app.mov
  1. Build Radio with SwiftUI

Looks like there is no built-in widget for Radio provided by Apple. Is it really a native iOS component?

Most of results I have found suggest building radio from a button with changing image source(selected/unselected, respectively), as I do in this SwiftUI code here: https://gist.github.com/huycozy/93b12c030651c4fde6fe8fceda1ba1ee. We can control what the reader vocalizes by setting traits and value for it manually. This being said, setting accessibility hint or value from Flutter side makes sense in this case. Please let me know if I am missing something.

Demo video
iOS.SwiftUI.mov

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

@huycozy huycozy self-assigned this Sep 24, 2025
@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. a: internationalization Supporting other languages or locales. (aka i18n) f: cupertino flutter/packages/flutter/cupertino repository labels Sep 24, 2025
@huycozy huycozy force-pushed the fix-RadioButton-is-not-vocalized-as-unselected-by-VoiceOver-170422 branch 2 times, most recently from 1073ed7 to 0c1a097 Compare September 25, 2025 03:01
@huycozy
Copy link
Member Author

huycozy commented Sep 25, 2025

/gemini

@gemini-code-assist
Copy link
Contributor

Thank you for the detailed explanation and the thorough solution to address the VoiceOver vocalization issue for unselected radio buttons on iOS/macOS. I appreciate the in-depth investigation into why simpler Semantics flags weren't sufficient and the careful consideration to avoid regressions on Android.

The approach of introducing a localized radioButtonUnselectedLabel and conditionally applying it as a semanticsHint for unselected radio buttons on the affected platforms is well-reasoned and robust. The addition of comprehensive tests across Cupertino, Material, and RawRadio widgets for both selected and unselected states on iOS and Android further strengthens the solution.

This is a great example of a meticulous and well-thought-out accessibility fix. Excellent work!

@huycozy huycozy force-pushed the fix-RadioButton-is-not-vocalized-as-unselected-by-VoiceOver-170422 branch from 0c1a097 to bfab2ac Compare September 25, 2025 07:34
@huycozy huycozy marked this pull request as ready for review September 25, 2025 08:16
@huycozy huycozy requested a review from chunhtai September 25, 2025 08:16
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 except one minor comment, @QuncCccccc do you know if we need to do anything to trigger translation once this pr landed?

@QuncCccccc
Copy link
Contributor

do you know if we need to do anything to trigger translation once this pr landed?

Once it's rolling into g3, the translation team will translate the new text. And when the translation is finished, we will need to back-integrate the translation to Flutter. So for now, there's nothing we need to do.

@chunhtai
Copy link
Contributor

do you know if we need to do anything to trigger translation once this pr landed?

Once it's rolling into g3, the translation team will translate the new text. And when the translation is finished, we will need to back-integrate the translation to Flutter. So for now, there's nothing we need to do.

Do we get a notification somehow? because I saw some of the old strings are not translated yet

@QuncCccccc
Copy link
Contributor

Do we get a notification somehow? because I saw some of the old strings are not translated yet

We don't get notification I think. Which old strings do you see? We can use go/flutter-l10n to check the status. Seems most of the languages have the 100% translation.

@chunhtai
Copy link
Contributor

noResultsFound and searchResultsFound was added recently, but hasn't been translated yet

Signed-off-by: huycozy <[email protected]>
@huycozy huycozy requested a review from chunhtai October 1, 2025 02: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

@chunhtai chunhtai added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 8, 2025
@auto-submit auto-submit bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Oct 8, 2025
@auto-submit
Copy link
Contributor

auto-submit bot commented Oct 8, 2025

autosubmit label was removed for flutter/flutter/175926, because The base commit of the PR is older than 7 days and can not be merged. Please merge the latest changes from the main into this branch and resubmit the PR.

@chunhtai chunhtai added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 8, 2025
@auto-submit auto-submit bot added this pull request to the merge queue Oct 9, 2025
Merged via the queue into flutter:master with commit c522865 Oct 9, 2025
77 checks passed
@flutter-dashboard flutter-dashboard bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Oct 9, 2025
reidbaker pushed a commit to AbdeMohlbi/flutter that referenced this pull request Dec 10, 2025
…175926)

### Issue
- Fix flutter#170422

### Description

This PR proposes a fix by adding a hint to Semantics here:


https://github.com/flutter/flutter/blob/b220f5a2abf53c28623514b223b20a46c69a8ca6/packages/flutter/lib/src/widgets/raw_radio.dart#L217-L222

The hint is only available on iOS &macOS platforms with a localized
string for `unselected` state to prevent regression on Android, which
works as expected currently and this seems to be the only solution as
investigated below ⬇️

<details open>
<summary>Demo the fix</summary>


https://github.com/user-attachments/assets/56c1c6c9-5178-45be-a633-47145a0543d6

</details>

#### Why can't we make it simpler with Semantics flags only?

- I looked at
[UIAccessibilityTraits](https://developer.apple.com/documentation/uikit/uiaccessibilitytraits),
it seems the
[selected](https://developer.apple.com/documentation/uikit/uiaccessibilitytraits/selected)
property does announce `selected` state, but it does not mention or give
hints for `unselected` state.
-
[L824-L828](https://github.com/flutter/flutter/blob/e8bef98051944f0faee9913f76d22f2d0cc91712/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm#L824-L828)
in Flutter source code does mention radio button: Looks like I can try
marking both toggle and check, but it's impossible, due to an assertion:
[A semantics node cannot be toggled and checked at the same
time](https://github.com/flutter/flutter/blob/35375e43fbae661f41dd8a6bda7454f8d55a4d54/packages/flutter/lib/src/rendering/object.dart#L4807-L4809)

➡️ Not sure which flags can trigger `unselected` state implicitly.

#### How do iOS native apps work?

I did test with two approaches:

1. iOS Reminders app: 
VoiceOver vocalizes as follows:
Unchecked radio: "...Incomplete..."
checked radio:"...Completed..."

<details>
<summary>Demo video</summary>


https://github.com/user-attachments/assets/6bed5d14-ded8-47c2-9c09-7afc266898cc

</details>

2. Build Radio with SwiftUI

Looks like [there is no built-in widget for Radio provided by
Apple](https://www.reddit.com/r/SwiftUI/comments/1gsh7zo/does_swiftui_have_a_builtin_radiobutton_in_2024/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button).
[Is it really a native iOS
component?](https://www.reddit.com/r/SwiftUI/comments/1gsh7zo/comment/lxea1uf/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button)

Most of results I have found suggest building radio from a button with
changing image source(selected/unselected, respectively), as I do in
this SwiftUI code here:
https://gist.github.com/huycozy/93b12c030651c4fde6fe8fceda1ba1ee. We can
control what the reader vocalizes by setting traits and value for it
manually. This being said, setting accessibility hint or value from
Flutter side makes sense in this case. Please let me know if I am
missing something.

<details>
<summary>Demo video</summary>


https://github.com/user-attachments/assets/118c89c5-46a3-497f-8b2a-6f7c5ae8edf3

</details>


## 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.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---------

Signed-off-by: huycozy <[email protected]>
Co-authored-by: chunhtai <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: internationalization Supporting other languages or locales. (aka i18n) f: cupertino flutter/packages/flutter/cupertino repository 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.

RadioButton is not vocalized as unselected by VoiceOver.

3 participants