mirror of
https://github.com/flutter/flutter.git
synced 2026-02-05 19:28:51 +08:00
This adds a new "radio group" accessibility role to `dart:ui` and the Flutter web engine. This does not update existing widgets to use this new role. Currently, users must manually add a `Semantics` widget to use this accessibility role. See the example app below. Part of: https://github.com/flutter/flutter/issues/162093 ## Example app <details> <summary>Example app that uses the radio group role...</summary> ```dart import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/semantics.dart'; void main() { runApp(const RadioExampleApp()); SemanticsBinding.instance.ensureSemantics(); } class RadioExampleApp extends StatelessWidget { const RadioExampleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('Radio Sample')), body: const Center(child: RadioExample()), ), ); } } class RadioExample extends StatefulWidget { const RadioExample({super.key}); @override State<RadioExample> createState() => _RadioExampleState(); } class _RadioExampleState extends State<RadioExample> { int _groupValue = 0; @override Widget build(BuildContext context) { return Semantics( label: 'Radio group', role: SemanticsRole.radioGroup, explicitChildNodes: true, child: Column( children: <Widget>[ ListTile( title: const Text('Foo'), leading: Radio<int>( value: 0, groupValue: _groupValue, onChanged: (int? value) => setState(() => _groupValue = value ?? 0), ), ), ListTile( title: const Text('Bar'), leading: Radio<int>( value: 1, groupValue: _groupValue, onChanged: (int? value) => setState(() => _groupValue = value ?? 0), ), ), ], ), ); } } ``` </details> <details> <summary>Accessibility tree...</summary> ``` SemanticsNode#0 │ Rect.fromLTRB(0.0, 0.0, 1200.0, 924.0) │ └─SemanticsNode#1 │ Rect.fromLTRB(0.0, 0.0, 1200.0, 924.0) │ textDirection: ltr │ └─SemanticsNode#2 │ Rect.fromLTRB(0.0, 0.0, 1200.0, 924.0) │ sortKey: OrdinalSortKey#83a1d(order: 0.0) │ └─SemanticsNode#3 │ Rect.fromLTRB(0.0, 0.0, 1200.0, 924.0) │ flags: scopesRoute │ ├─SemanticsNode#9 │ │ Rect.fromLTRB(0.0, 0.0, 1200.0, 56.0) │ │ │ └─SemanticsNode#10 │ Rect.fromLTRB(532.6, 14.0, 667.4, 42.0) │ flags: isHeader │ label: "Radio Sample" │ textDirection: ltr │ └─SemanticsNode#4 │ Rect.fromLTRB(0.0, 56.0, 1200.0, 924.0) │ label: "Radio group" │ textDirection: ltr │ role: radioGroup │ ├─SemanticsNode#5 │ │ Rect.fromLTRB(0.0, 0.0, 1200.0, 48.0) │ │ flags: hasSelectedState, hasEnabledState, isEnabled │ │ label: "Foo" │ │ textDirection: ltr │ │ │ └─SemanticsNode#6 │ Rect.fromLTRB(16.0, 8.0, 48.0, 40.0) │ actions: focus, tap │ flags: hasCheckedState, hasSelectedState, hasEnabledState, │ isEnabled, isInMutuallyExclusiveGroup, isFocusable │ └─SemanticsNode#7 │ Rect.fromLTRB(0.0, 48.0, 1200.0, 96.0) │ flags: hasSelectedState, hasEnabledState, isEnabled │ label: "Bar" │ textDirection: ltr │ └─SemanticsNode#8 Rect.fromLTRB(16.0, 8.0, 48.0, 40.0) actions: focus, tap flags: hasCheckedState, isChecked, hasSelectedState, isSelected, hasEnabledState, isEnabled, isInMutuallyExclusiveGroup, isFocusable ``` </details> <details> <summary>HTML generated by Flutter web...</summary> ```html <html> ... <body flt-embedding="full-page" flt-renderer="canvaskit" flt-build-mode="debug" spellcheck="false" style="..."> ... <flt-announcement-host> <flt-announcement-polite aria-live="polite" style="..."> <flt-announcement-assertive aria-live="assertive" style="..."> <flutter-view style="..."> <flt-glass-pane></flt-glass-pane> <flt-text-editing-host></flt-text-editing-host> <flt-semantics-host style="..."> <flt-semantics id="flt-semantic-node-0" style="..."> <flt-semantics-container style="..."> <flt-semantics id="flt-semantic-node-1" style="..."> <flt-semantics-container style="..."> <flt-semantics id="flt-semantic-node-2" style="..."> <flt-semantics-container style="..."> <flt-semantics id="flt-semantic-node-3" role="dialog" style="..."> <flt-semantics-container style="..."> <flt-semantics id="flt-semantic-node-9" style="..."> <flt-semantics-container style="..."> <h2 id="flt-semantic-node-10" tabindex="-1" style="..."> Radio Sample</h2> </flt-semantics-container> </flt-semantics> <flt-semantics id="flt-semantic-node-4" role="radiogroup" aria-label="Radio group" style="..."> <flt-semantics-container style="..."> <flt-semantics id="flt-semantic-node-5" role="group" aria-label="Foo" aria-selected="false" style="..."> <flt-semantics-container style="..."> <flt-semantics id="flt-semantic-node-6" tabindex="0" flt-tappable="" role="radio" aria-checked="false" style="..."> </flt-semantics> </flt-semantics-container> </flt-semantics> <flt-semantics id="flt-semantic-node-7" role="group" aria-label="Bar" aria-selected="false" style="..."> <flt-semantics-container style="..."> <flt-semantics id="flt-semantic-node-8" tabindex="0" flt-tappable="" role="radio" aria-checked="true" style="..."> </flt-semantics> </flt-semantics-container> </flt-semantics> </flt-semantics-container> </flt-semantics> </flt-semantics-container> </flt-semantics> </flt-semantics-container> </flt-semantics> </flt-semantics-container> </flt-semantics> </flt-semantics-container> </flt-semantics> </flt-semantics-host> </body> </html> ``` </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]. <!-- 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