Skip to content

Commit 6467b99

Browse files
hannah-hyjexaby73
authored andcommitted
Add optional labelText and semanticLabel to Checkbox (flutter#124555)
Re-open from flutter#116551 This PR added optional labelText (which will be rendered side by side with Checkbox in the future, and thus is also announced by default by screen readers), and semanticLabel(which will be announced by screen reader if provided, overrides labelText, in order to do that we might want to wrap the Text widget inside ExcludeSemantics in the future). Issues fixed: [b/239564167]
1 parent def854b commit 6467b99

File tree

4 files changed

+69
-0
lines changed

4 files changed

+69
-0
lines changed

packages/flutter/lib/src/material/checkbox.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class Checkbox extends StatefulWidget {
9090
this.shape,
9191
this.side,
9292
this.isError = false,
93+
this.semanticLabel,
9394
}) : _checkboxType = _CheckboxType.material,
9495
assert(tristate || value != null);
9596

@@ -129,6 +130,7 @@ class Checkbox extends StatefulWidget {
129130
this.shape,
130131
this.side,
131132
this.isError = false,
133+
this.semanticLabel,
132134
}) : _checkboxType = _CheckboxType.adaptive,
133135
assert(tristate || value != null);
134136

@@ -386,6 +388,15 @@ class Checkbox extends StatefulWidget {
386388
/// Must not be null. Defaults to false.
387389
final bool isError;
388390

391+
/// {@template flutter.material.checkbox.semanticLabel}
392+
/// The semantic label for the checkobox that will be announced by screen readers.
393+
///
394+
/// This is announced in accessibility modes (e.g TalkBack/VoiceOver).
395+
///
396+
/// This label does not show in the UI.
397+
/// {@endtemplate}
398+
final String? semanticLabel;
399+
389400
/// The width of a checkbox widget.
390401
static const double width = 18.0;
391402

@@ -568,6 +579,7 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg
568579
?? defaults.splashRadius!;
569580

570581
return Semantics(
582+
label: widget.semanticLabel,
571583
checked: widget.value ?? false,
572584
mixed: widget.tristate ? widget.value == null : null,
573585
child: buildToggleable(

packages/flutter/lib/src/material/checkbox_list_tile.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ class CheckboxListTile extends StatelessWidget {
194194
this.selectedTileColor,
195195
this.onFocusChange,
196196
this.enableFeedback,
197+
this.checkboxSemanticLabel,
197198
}) : _checkboxType = _CheckboxType.material,
198199
assert(tristate || value != null),
199200
assert(!isThreeLine || subtitle != null);
@@ -237,6 +238,7 @@ class CheckboxListTile extends StatelessWidget {
237238
this.selectedTileColor,
238239
this.onFocusChange,
239240
this.enableFeedback,
241+
this.checkboxSemanticLabel,
240242
}) : _checkboxType = _CheckboxType.adaptive,
241243
assert(tristate || value != null),
242244
assert(!isThreeLine || subtitle != null);
@@ -452,6 +454,9 @@ class CheckboxListTile extends StatelessWidget {
452454
/// inoperative.
453455
final bool? enabled;
454456

457+
/// {@macro flutter.material.checkbox.semanticLabel}
458+
final String? checkboxSemanticLabel;
459+
455460
final _CheckboxType _checkboxType;
456461

457462
void _handleValueChange() {
@@ -488,6 +493,7 @@ class CheckboxListTile extends StatelessWidget {
488493
shape: checkboxShape,
489494
side: side,
490495
isError: isError,
496+
semanticLabel: checkboxSemanticLabel,
491497
);
492498
case _CheckboxType.adaptive:
493499
control = Checkbox.adaptive(
@@ -506,6 +512,7 @@ class CheckboxListTile extends StatelessWidget {
506512
shape: checkboxShape,
507513
side: side,
508514
isError: isError,
515+
semanticLabel: checkboxSemanticLabel,
509516
);
510517
}
511518

packages/flutter/test/material/checkbox_list_tile_test.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,31 @@ void main() {
993993
expect(feedback.hapticCount, 0);
994994
});
995995
});
996+
997+
testWidgets('CheckboxListTile has proper semantics', (WidgetTester tester) async {
998+
final List<dynamic> log = <dynamic>[];
999+
final SemanticsHandle handle = tester.ensureSemantics();
1000+
await tester.pumpWidget(wrap(
1001+
child: CheckboxListTile(
1002+
value: true,
1003+
onChanged: (bool? value) { log.add(value); },
1004+
title: const Text('Hello'),
1005+
checkboxSemanticLabel: 'there',
1006+
),
1007+
));
1008+
1009+
expect(tester.getSemantics(find.byType(CheckboxListTile)), matchesSemantics(
1010+
hasCheckedState: true,
1011+
isChecked: true,
1012+
hasEnabledState: true,
1013+
isEnabled: true,
1014+
hasTapAction: true,
1015+
isFocusable: true,
1016+
label: 'Hello\nthere',
1017+
));
1018+
1019+
handle.dispose();
1020+
});
9961021
}
9971022

9981023
class _SelectedGrabMouseCursor extends MaterialStateMouseCursor {

packages/flutter/test/material/checkbox_test.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,31 @@ void main() {
191191
hasEnabledState: true,
192192
));
193193

194+
// Check if semanticLabel is there.
195+
await tester.pumpWidget(Directionality(
196+
textDirection: TextDirection.ltr,
197+
child: Theme(
198+
data: theme,
199+
child: Material(
200+
child: Checkbox(
201+
semanticLabel: 'checkbox',
202+
value: true,
203+
onChanged: (bool? b) { },
204+
),
205+
),
206+
),
207+
));
208+
209+
expect(tester.getSemantics(find.byType(Focus)), matchesSemantics(
210+
label: 'checkbox',
211+
textDirection: TextDirection.ltr,
212+
hasCheckedState: true,
213+
hasEnabledState: true,
214+
isChecked: true,
215+
isEnabled: true,
216+
hasTapAction: true,
217+
isFocusable: true,
218+
));
194219
handle.dispose();
195220
});
196221

0 commit comments

Comments
 (0)