Skip to content

Commit

Permalink
Add optional labelText and semanticLabel to Checkbox (flutter#124555)
Browse files Browse the repository at this point in the history
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]
  • Loading branch information
hangyujin committed Apr 14, 2023
1 parent 094bd85 commit 4d6ef78
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 0 deletions.
12 changes: 12 additions & 0 deletions packages/flutter/lib/src/material/checkbox.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class Checkbox extends StatefulWidget {
this.shape,
this.side,
this.isError = false,
this.semanticLabel,
}) : _checkboxType = _CheckboxType.material,
assert(tristate || value != null);

Expand Down Expand Up @@ -129,6 +130,7 @@ class Checkbox extends StatefulWidget {
this.shape,
this.side,
this.isError = false,
this.semanticLabel,
}) : _checkboxType = _CheckboxType.adaptive,
assert(tristate || value != null);

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

/// {@template flutter.material.checkbox.semanticLabel}
/// The semantic label for the checkobox that will be announced by screen readers.
///
/// This is announced in accessibility modes (e.g TalkBack/VoiceOver).
///
/// This label does not show in the UI.
/// {@endtemplate}
final String? semanticLabel;

/// The width of a checkbox widget.
static const double width = 18.0;

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

return Semantics(
label: widget.semanticLabel,
checked: widget.value ?? false,
mixed: widget.tristate ? widget.value == null : null,
child: buildToggleable(
Expand Down
7 changes: 7 additions & 0 deletions packages/flutter/lib/src/material/checkbox_list_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ class CheckboxListTile extends StatelessWidget {
this.selectedTileColor,
this.onFocusChange,
this.enableFeedback,
this.checkboxSemanticLabel,
}) : _checkboxType = _CheckboxType.material,
assert(tristate || value != null),
assert(!isThreeLine || subtitle != null);
Expand Down Expand Up @@ -237,6 +238,7 @@ class CheckboxListTile extends StatelessWidget {
this.selectedTileColor,
this.onFocusChange,
this.enableFeedback,
this.checkboxSemanticLabel,
}) : _checkboxType = _CheckboxType.adaptive,
assert(tristate || value != null),
assert(!isThreeLine || subtitle != null);
Expand Down Expand Up @@ -452,6 +454,9 @@ class CheckboxListTile extends StatelessWidget {
/// inoperative.
final bool? enabled;

/// {@macro flutter.material.checkbox.semanticLabel}
final String? checkboxSemanticLabel;

final _CheckboxType _checkboxType;

void _handleValueChange() {
Expand Down Expand Up @@ -488,6 +493,7 @@ class CheckboxListTile extends StatelessWidget {
shape: checkboxShape,
side: side,
isError: isError,
semanticLabel: checkboxSemanticLabel,
);
case _CheckboxType.adaptive:
control = Checkbox.adaptive(
Expand All @@ -506,6 +512,7 @@ class CheckboxListTile extends StatelessWidget {
shape: checkboxShape,
side: side,
isError: isError,
semanticLabel: checkboxSemanticLabel,
);
}

Expand Down
25 changes: 25 additions & 0 deletions packages/flutter/test/material/checkbox_list_tile_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,31 @@ void main() {
expect(feedback.hapticCount, 0);
});
});

testWidgets('CheckboxListTile has proper semantics', (WidgetTester tester) async {
final List<dynamic> log = <dynamic>[];
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(wrap(
child: CheckboxListTile(
value: true,
onChanged: (bool? value) { log.add(value); },
title: const Text('Hello'),
checkboxSemanticLabel: 'there',
),
));

expect(tester.getSemantics(find.byType(CheckboxListTile)), matchesSemantics(
hasCheckedState: true,
isChecked: true,
hasEnabledState: true,
isEnabled: true,
hasTapAction: true,
isFocusable: true,
label: 'Hello\nthere',
));

handle.dispose();
});
}

class _SelectedGrabMouseCursor extends MaterialStateMouseCursor {
Expand Down
25 changes: 25 additions & 0 deletions packages/flutter/test/material/checkbox_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,31 @@ void main() {
hasEnabledState: true,
));

// Check if semanticLabel is there.
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: Theme(
data: theme,
child: Material(
child: Checkbox(
semanticLabel: 'checkbox',
value: true,
onChanged: (bool? b) { },
),
),
),
));

expect(tester.getSemantics(find.byType(Focus)), matchesSemantics(
label: 'checkbox',
textDirection: TextDirection.ltr,
hasCheckedState: true,
hasEnabledState: true,
isChecked: true,
isEnabled: true,
hasTapAction: true,
isFocusable: true,
));
handle.dispose();
});

Expand Down

0 comments on commit 4d6ef78

Please sign in to comment.