Repository: ubuntu/yaru.dart

Issue:
Create control tiles
![](https://cdn.discordapp.com/attachments/860793709912915988/1040316522884759653/image.png)
![](https://cdn.discordapp.com/attachments/860793709912915988/1040316523455201400/image.png)

I think I will just add a new `tile` boolean property to our control buttons, which will replace the `GestureDetector` by an `InkWell` with a border radius.


Discussion: I finally think it would be simpler to use a `ListTile`, because `YaruTogglableButton` is really too much different.
So I will do something similar to material finally, I mean a simple widget which mix control and list tile.

Fix patch:
diff --git a/example/lib/example_page_items.dart b/example/lib/example_page_items.dart
index 1c02238fa..c4852f69d 100644
--- a/example/lib/example_page_items.dart
+++ b/example/lib/example_page_items.dart
@@ -4,6 +4,7 @@ import 'pages/banner_page.dart';
 import 'pages/carousel_page.dart';
 import 'pages/check_button_page.dart';
 import 'pages/color_disk_page.dart';
+import 'pages/control_tiles_page.dart';
 import 'pages/controls_page.dart';
 import 'pages/dialog_page.dart';
 import 'pages/draggable_page.dart';
@@ -81,6 +82,15 @@ final examplePageItems = <PageItem>[
     pageBuilder: (context) => const ColorDiskPage(),
     iconBuilder: (context, selected) => const Icon(YaruIcons.color_select),
   ),
+  PageItem(
+    titleBuilder: (context) => const Text('YaruControlListTiles'),
+    tooltipMessage: 'YaruControlListTiles',
+    snippetUrl:
+        'https://raw.githubusercontent.com/ubuntu/yaru_widgets.dart/main/example/lib/pages/control_tiles_page.dart',
+    pageBuilder: (context) => const ControlListTilesPage(),
+    iconBuilder: (context, selected) =>
+        const Icon(YaruIcons.format_ordered_list),
+  ),
   PageItem(
     titleBuilder: (context) => const Text('YaruDraggable'),
     tooltipMessage: 'YaruDraggable',
diff --git a/example/lib/pages/control_tiles_page.dart b/example/lib/pages/control_tiles_page.dart
new file mode 100644
index 000000000..e64ac99d8
--- /dev/null
+++ b/example/lib/pages/control_tiles_page.dart
@@ -0,0 +1,48 @@
+import 'package:flutter/material.dart';
+import 'package:yaru_widgets/yaru_widgets.dart';
+
+class ControlListTilesPage extends StatefulWidget {
+  const ControlListTilesPage({super.key});
+
+  @override
+  _ControlListTilesPageState createState() => _ControlListTilesPageState();
+}
+
+class _ControlListTilesPageState extends State<ControlListTilesPage> {
+  final List<bool?> _checkboxValues = [false, null, true];
+  int? _radioValue = 1;
+  final List<bool> _switchValues = List.generate(3, (i) => i % 2 == 0);
+
+  @override
+  Widget build(BuildContext context) {
+    return ListView(
+      padding: const EdgeInsets.all(kYaruPagePadding),
+      children: [
+        for (var i = 0; i < _checkboxValues.length; ++i)
+          YaruCheckboxListTile(
+            value: _checkboxValues[i],
+            onChanged: (v) => setState(() => _checkboxValues[i] = v),
+            tristate: true,
+            title: const Text('YaruCheckboxListTile'),
+          ),
+        const Divider(),
+        for (var i = 0; i < 3; ++i)
+          YaruRadioListTile<int>(
+            value: i,
+            groupValue: _radioValue,
+            onChanged: (v) => setState(() => _radioValue = v),
+            toggleable: true,
+            title: const Text('YaruRadioListTile'),
+          ),
+        const Divider(),
+        for (var i = 0; i < _switchValues.length; ++i)
+          YaruSwitchListTile(
+            value: _switchValues[i],
+            onChanged: (v) => setState(() => _switchValues[i] = v),
+            title: const Text('YaruSwitchListTile'),
+          ),
+        const SizedBox(height: 10),
+      ],
+    );
+  }
+}
diff --git a/lib/src/controls/yaru_checkbox_list_tile.dart b/lib/src/controls/yaru_checkbox_list_tile.dart
new file mode 100644
index 000000000..d1a300a23
--- /dev/null
+++ b/lib/src/controls/yaru_checkbox_list_tile.dart
@@ -0,0 +1,160 @@
+import 'package:flutter/material.dart';
+
+import 'yaru_check_button.dart';
+import 'yaru_checkbox.dart';
+import 'yaru_radio_list_tile.dart';
+import 'yaru_switch_list_tile.dart';
+
+/// A [ListTile] with a [YaruCheckbox]. In other words, a checkbox with a label.
+///
+/// See [CheckboxListTile] for more details.
+///
+/// See also:
+///
+///  * [ListTileTheme], which can be used to affect the style of list tiles,
+///    including checkbox list tiles.
+///  * [YaruCheckButton], a similar widget with a desktop style.
+///  * [YaruRadioListTile], a similar widget for radio buttons.
+///  * [YaruSwitchListTile], a similar widget for switches.
+///  * [ListTile] and [YaruCheckbox], the widgets from which this widget is made.
+class YaruCheckboxListTile extends StatelessWidget {
+  /// Creates a combination of a [ListTile] and a [YaruCheckbox].
+  ///
+  /// See [CheckboxListTile].
+  const YaruCheckboxListTile({
+    super.key,
+    required this.value,
+    required this.onChanged,
+    this.tileColor,
+    this.title,
+    this.subtitle,
+    this.isThreeLine = false,
+    this.dense,
+    this.secondary,
+    this.selected = false,
+    this.controlAffinity = ListTileControlAffinity.platform,
+    this.autofocus = false,
+    this.contentPadding,
+    this.tristate = false,
+    this.shape,
+    this.selectedTileColor,
+    this.visualDensity,
+    this.focusNode,
+    this.enableFeedback,
+  })  : assert(tristate || value != null),
+        assert(!isThreeLine || subtitle != null);
+
+  /// See [CheckboxListTile.value].
+  final bool? value;
+
+  /// See [CheckboxListTile.onChanged].
+  final ValueChanged<bool?>? onChanged;
+
+  /// See [CheckboxListTile.tileColor].
+  final Color? tileColor;
+
+  /// See [CheckboxListTile.title].
+  final Widget? title;
+
+  /// See [CheckboxListTile.subtitle].
+  final Widget? subtitle;
+
+  /// See [CheckboxListTile.secondary].
+  final Widget? secondary;
+
+  /// See [CheckboxListTile.isThreeLine].
+  final bool isThreeLine;
+
+  /// See [CheckboxListTile.dense].
+  final bool? dense;
+
+  /// See [CheckboxListTile.selected].
+  final bool selected;
+
+  /// See [CheckboxListTile.controlAffinity].
+  final ListTileControlAffinity controlAffinity;
+
+  /// See [CheckboxListTile.autofocus].
+  final bool autofocus;
+
+  /// See [CheckboxListTile.contentPadding].
+  final EdgeInsetsGeometry? contentPadding;
+
+  /// See [CheckboxListTile.tristate].
+  final bool tristate;
+
+  /// See [CheckboxListTile.shape].
+  final ShapeBorder? shape;
+
+  /// See [CheckboxListTile.selectedTileColor].
+  final Color? selectedTileColor;
+
+  /// See [CheckboxListTile.visualDensity].
+  final VisualDensity? visualDensity;
+
+  /// See [CheckboxListTile.focusNode].
+  final FocusNode? focusNode;
+
+  /// See [CheckboxListTile.enableFeedback].
+  final bool? enableFeedback;
+
+  void _handleValueChange() {
+    assert(onChanged != null);
+    switch (value) {
+      case false:
+        onChanged!(true);
+        break;
+      case true:
+        onChanged!(tristate ? null : false);
+        break;
+      case null:
+        onChanged!(false);
+        break;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    Widget? leading, trailing;
+    final Widget control = YaruCheckbox(
+      value: value,
+      onChanged: onChanged,
+      autofocus: autofocus,
+      tristate: tristate,
+    );
+
+    switch (controlAffinity) {
+      case ListTileControlAffinity.leading:
+        leading = control;
+        trailing = secondary;
+        break;
+      case ListTileControlAffinity.trailing:
+      case ListTileControlAffinity.platform:
+        leading = secondary;
+        trailing = control;
+        break;
+    }
+
+    return MergeSemantics(
+      child: ListTile(
+        leading: leading,
+        title: title,
+        subtitle: subtitle,
+        trailing: trailing,
+        isThreeLine: isThreeLine,
+        dense: dense,
+        enabled: onChanged != null,
+        onTap: onChanged != null ? _handleValueChange : null,
+        selected: selected,
+        autofocus: autofocus,
+        contentPadding: contentPadding,
+        shape: shape,
+        selectedTileColor: selectedTileColor,
+        tileColor: tileColor,
+        visualDensity: visualDensity,
+        focusNode: focusNode,
+        enableFeedback: enableFeedback,
+      ),
+    );
+  }
+}
diff --git a/lib/src/controls/yaru_radio_list_tile.dart b/lib/src/controls/yaru_radio_list_tile.dart
new file mode 100644
index 000000000..97c9bfa2b
--- /dev/null
+++ b/lib/src/controls/yaru_radio_list_tile.dart
@@ -0,0 +1,158 @@
+import 'package:flutter/material.dart';
+
+import 'yaru_checkbox_list_tile.dart';
+import 'yaru_radio.dart';
+import 'yaru_radio_button.dart';
+import 'yaru_switch_list_tile.dart';
+
+/// A [ListTile] with a [YaruRadio]. In other words, a radio with a label.
+///
+/// See [RadioListTile] for more details.
+///
+/// See also:
+///
+///  * [ListTileTheme], which can be used to affect the style of list tiles,
+///    including radio list tiles.
+///  * [YaruRadioButton], a similar widget with a desktop style.
+///  * [YaruCheckboxListTile], a similar widget for checkboxes.
+///  * [YaruSwitchListTile], a similar widget for switches.
+///  * [ListTile] and [YaruRadio], the widgets from which this widget is made.
+class YaruRadioListTile<T> extends StatelessWidget {
+  /// Creates a combination of a [ListTile] and a [YaruRadio].
+  ///
+  /// See [RadioListTile].
+  const YaruRadioListTile({
+    super.key,
+    required this.value,
+    required this.groupValue,
+    required this.onChanged,
+    this.toggleable = false,
+    this.title,
+    this.subtitle,
+    this.isThreeLine = false,
+    this.dense,
+    this.secondary,
+    this.selected = false,
+    this.controlAffinity = ListTileControlAffinity.platform,
+    this.autofocus = false,
+    this.contentPadding,
+    this.shape,
+    this.tileColor,
+    this.selectedTileColor,
+    this.visualDensity,
+    this.focusNode,
+    this.enableFeedback,
+  }) : assert(!isThreeLine || subtitle != null);
+
+  /// See [RadioListTile.value].
+  final T value;
+
+  /// See [RadioListTile.groupValue].
+  final T? groupValue;
+
+  /// See [RadioListTile.onChanged].
+  final ValueChanged<T?>? onChanged;
+
+  /// See [RadioListTile.toggleable].
+  final bool toggleable;
+
+  /// See [RadioListTile.title].
+  final Widget? title;
+
+  /// See [RadioListTile.subtitle].
+  final Widget? subtitle;
+
+  /// See [RadioListTile.secondary].
+  final Widget? secondary;
+
+  /// See [RadioListTile.isThreeLine].
+  final bool isThreeLine;
+
+  /// See [RadioListTile.dense].
+  final bool? dense;
+
+  /// See [RadioListTile.selected].
+  final bool selected;
+
+  /// See [RadioListTile.controlAffinity].
+  final ListTileControlAffinity controlAffinity;
+
+  /// See [RadioListTile.autofocus].
+  final bool autofocus;
+
+  /// See [RadioListTile.contentPadding].
+  final EdgeInsetsGeometry? contentPadding;
+
+  /// See [RadioListTile.shape].
+  final ShapeBorder? shape;
+
+  /// See [RadioListTile.tileColor].
+  final Color? tileColor;
+
+  /// See [RadioListTile.selectedTileColor].
+  final Color? selectedTileColor;
+
+  /// See [RadioListTile.visualDensity].
+  final VisualDensity? visualDensity;
+
+  /// See [RadioListTile.focusNode].
+  final FocusNode? focusNode;
+
+  /// See [RadioListTile.enableFeedback].
+  final bool? enableFeedback;
+
+  void _handleValueChange() {
+    assert(onChanged != null);
+    if (groupValue != value || !toggleable) {
+      onChanged!(value);
+    } else if (toggleable) {
+      onChanged!(null);
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    Widget? leading, trailing;
+    final Widget control = YaruRadio<T>(
+      value: value,
+      groupValue: groupValue,
+      onChanged: onChanged,
+      toggleable: toggleable,
+      autofocus: autofocus,
+    );
+
+    switch (controlAffinity) {
+      case ListTileControlAffinity.leading:
+      case ListTileControlAffinity.platform:
+        leading = control;
+        trailing = secondary;
+        break;
+      case ListTileControlAffinity.trailing:
+        leading = secondary;
+        trailing = control;
+        break;
+    }
+
+    return MergeSemantics(
+      child: ListTile(
+        leading: leading,
+        title: title,
+        subtitle: subtitle,
+        trailing: trailing,
+        isThreeLine: isThreeLine,
+        dense: dense,
+        enabled: onChanged != null,
+        shape: shape,
+        tileColor: tileColor,
+        selectedTileColor: selectedTileColor,
+        onTap: onChanged != null ? _handleValueChange : null,
+        selected: selected,
+        autofocus: autofocus,
+        contentPadding: contentPadding,
+        visualDensity: visualDensity,
+        focusNode: focusNode,
+        enableFeedback: enableFeedback,
+      ),
+    );
+  }
+}
diff --git a/lib/src/controls/yaru_switch_list_tile.dart b/lib/src/controls/yaru_switch_list_tile.dart
new file mode 100644
index 000000000..b2eb86a75
--- /dev/null
+++ b/lib/src/controls/yaru_switch_list_tile.dart
@@ -0,0 +1,148 @@
+import 'package:flutter/material.dart';
+
+import 'yaru_checkbox_list_tile.dart';
+import 'yaru_radio_list_tile.dart';
+import 'yaru_switch.dart';
+import 'yaru_switch_button.dart';
+
+/// A [ListTile] with a [YaruSwitch]. In other words, a switch with a label.
+///
+/// See [SwitchListTile] for more details.
+///
+/// See also:
+///
+///  * [ListTileTheme], which can be used to affect the style of list tiles,
+///    including switch list tiles.
+///  * [YaruSwitchButton], a similar widget with a desktop style.
+///  * [YaruCheckboxListTile], a similar widget for checkboxes.
+///  * [YaruRadioListTile], a similar widget for radio buttons.
+///  * [ListTile] and [YaruSwitch], the widgets from which this widget is made.
+class YaruSwitchListTile extends StatelessWidget {
+  /// Creates a combination of a [ListTile] and a [YaruSwitch].
+  ///
+  /// See [SwitchListTile].
+  const YaruSwitchListTile({
+    super.key,
+    required this.value,
+    required this.onChanged,
+    this.tileColor,
+    this.title,
+    this.subtitle,
+    this.isThreeLine = false,
+    this.dense,
+    this.contentPadding,
+    this.secondary,
+    this.selected = false,
+    this.autofocus = false,
+    this.controlAffinity = ListTileControlAffinity.platform,
+    this.shape,
+    this.selectedTileColor,
+    this.visualDensity,
+    this.focusNode,
+    this.enableFeedback,
+    this.hoverColor,
+  }) : assert(!isThreeLine || subtitle != null);
+
+  /// See [SwitchListTile.value].
+  final bool value;
+
+  /// See [SwitchListTile.onChanged].
+  final ValueChanged<bool>? onChanged;
+
+  /// See [SwitchListTile.tileColor].
+  final Color? tileColor;
+
+  /// See [SwitchListTile.title].
+  final Widget? title;
+
+  /// See [SwitchListTile.subtitle].
+  final Widget? subtitle;
+
+  /// See [SwitchListTile.secondary].
+  final Widget? secondary;
+
+  /// See [SwitchListTile.isThreeLine].
+  final bool isThreeLine;
+
+  /// See [SwitchListTile.dense].
+  final bool? dense;
+
+  /// See [SwitchListTile.contentPadding].
+  final EdgeInsetsGeometry? contentPadding;
+
+  /// See [SwitchListTile.selected].
+  final bool selected;
+
+  /// See [SwitchListTile.autofocus].
+  final bool autofocus;
+
+  /// See [SwitchListTile.controlAffinity].
+  final ListTileControlAffinity controlAffinity;
+
+  /// See [SwitchListTile.shape].
+  final ShapeBorder? shape;
+
+  /// See [SwitchListTile.selectedTileColor].
+  final Color? selectedTileColor;
+
+  /// See [SwitchListTile.visualDensity].
+  final VisualDensity? visualDensity;
+
+  /// See [SwitchListTile.focusNode].
+  final FocusNode? focusNode;
+
+  /// See [SwitchListTile.enableFeedback].
+  final bool? enableFeedback;
+
+  /// See [SwitchListTile.hoverColor].
+  final Color? hoverColor;
+
+  @override
+  Widget build(BuildContext context) {
+    final control = YaruSwitch(
+      value: value,
+      onChanged: onChanged,
+      autofocus: autofocus,
+    );
+
+    Widget? leading, trailing;
+    switch (controlAffinity) {
+      case ListTileControlAffinity.leading:
+        leading = control;
+        trailing = secondary;
+        break;
+      case ListTileControlAffinity.trailing:
+      case ListTileControlAffinity.platform:
+        leading = secondary;
+        trailing = control;
+        break;
+    }
+
+    return MergeSemantics(
+      child: ListTile(
+        leading: leading,
+        title: title,
+        subtitle: subtitle,
+        trailing: trailing,
+        isThreeLine: isThreeLine,
+        dense: dense,
+        contentPadding: contentPadding,
+        enabled: onChanged != null,
+        onTap: onChanged != null
+            ? () {
+                onChanged!(!value);
+              }
+            : null,
+        selected: selected,
+        selectedTileColor: selectedTileColor,
+        autofocus: autofocus,
+        shape: shape,
+        tileColor: tileColor,
+        visualDensity: visualDensity,
+        focusNode: focusNode,
+        enableFeedback: enableFeedback,
+        hoverColor: hoverColor,
+      ),
+    );
+  }
+}
diff --git a/lib/yaru_widgets.dart b/lib/yaru_widgets.dart
index 2a86789fa..3484fa9a5 100644
--- a/lib/yaru_widgets.dart
+++ b/lib/yaru_widgets.dart
@@ -6,6 +6,7 @@ export 'src/constants.dart';
 export 'src/controls/yaru_back_button.dart';
 export 'src/controls/yaru_check_button.dart';
 export 'src/controls/yaru_checkbox.dart';
+export 'src/controls/yaru_checkbox_list_tile.dart';
 export 'src/controls/yaru_circular_progress_indicator.dart';
 export 'src/controls/yaru_close_button.dart';
 export 'src/controls/yaru_color_disk.dart';
@@ -15,8 +16,10 @@ export 'src/controls/yaru_option_button.dart';
 export 'src/controls/yaru_popup_menu_button.dart';
 export 'src/controls/yaru_radio.dart';
 export 'src/controls/yaru_radio_button.dart';
+export 'src/controls/yaru_radio_list_tile.dart';
 export 'src/controls/yaru_switch.dart';
 export 'src/controls/yaru_switch_button.dart';
+export 'src/controls/yaru_switch_list_tile.dart';
 export 'src/controls/yaru_title_bar.dart';
 export 'src/controls/yaru_toggle_button.dart';
 export 'src/controls/yaru_toggle_button_theme.dart';


Test patch:
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-checked-dark.png b/test/controls/goldens/yaru_checkbox_list_tile-checked-dark.png
new file mode 100644
index 000000000..0f8848347
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-checked-dark.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-checked-disabled-dark.png b/test/controls/goldens/yaru_checkbox_list_tile-checked-disabled-dark.png
new file mode 100644
index 000000000..5295ebe9e
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-checked-disabled-dark.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-checked-disabled-light.png b/test/controls/goldens/yaru_checkbox_list_tile-checked-disabled-light.png
new file mode 100644
index 000000000..a8f6178c1
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-checked-disabled-light.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-checked-focused-dark.png b/test/controls/goldens/yaru_checkbox_list_tile-checked-focused-dark.png
new file mode 100644
index 000000000..4de2cfe4b
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-checked-focused-dark.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-checked-focused-light.png b/test/controls/goldens/yaru_checkbox_list_tile-checked-focused-light.png
new file mode 100644
index 000000000..84e029393
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-checked-focused-light.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-checked-hovered-dark.png b/test/controls/goldens/yaru_checkbox_list_tile-checked-hovered-dark.png
new file mode 100644
index 000000000..18e5ff162
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-checked-hovered-dark.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-checked-hovered-light.png b/test/controls/goldens/yaru_checkbox_list_tile-checked-hovered-light.png
new file mode 100644
index 000000000..52583b924
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-checked-hovered-light.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-checked-light.png b/test/controls/goldens/yaru_checkbox_list_tile-checked-light.png
new file mode 100644
index 000000000..909bf6e79
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-checked-light.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-checked-pressed-dark.png b/test/controls/goldens/yaru_checkbox_list_tile-checked-pressed-dark.png
new file mode 100644
index 000000000..44df81bf4
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-checked-pressed-dark.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-checked-pressed-light.png b/test/controls/goldens/yaru_checkbox_list_tile-checked-pressed-light.png
new file mode 100644
index 000000000..bae810bef
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-checked-pressed-light.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-tristate-dark.png b/test/controls/goldens/yaru_checkbox_list_tile-tristate-dark.png
new file mode 100644
index 000000000..f2b471b31
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-tristate-dark.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-tristate-disabled-dark.png b/test/controls/goldens/yaru_checkbox_list_tile-tristate-disabled-dark.png
new file mode 100644
index 000000000..ca088a464
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-tristate-disabled-dark.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-tristate-disabled-light.png b/test/controls/goldens/yaru_checkbox_list_tile-tristate-disabled-light.png
new file mode 100644
index 000000000..da71e13f3
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-tristate-disabled-light.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-tristate-focused-dark.png b/test/controls/goldens/yaru_checkbox_list_tile-tristate-focused-dark.png
new file mode 100644
index 000000000..44b9f17e1
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-tristate-focused-dark.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-tristate-focused-light.png b/test/controls/goldens/yaru_checkbox_list_tile-tristate-focused-light.png
new file mode 100644
index 000000000..791dc071a
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-tristate-focused-light.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-tristate-hovered-dark.png b/test/controls/goldens/yaru_checkbox_list_tile-tristate-hovered-dark.png
new file mode 100644
index 000000000..324536f18
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-tristate-hovered-dark.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-tristate-hovered-light.png b/test/controls/goldens/yaru_checkbox_list_tile-tristate-hovered-light.png
new file mode 100644
index 000000000..7c53857ff
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-tristate-hovered-light.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-tristate-light.png b/test/controls/goldens/yaru_checkbox_list_tile-tristate-light.png
new file mode 100644
index 000000000..c4ecdfb6d
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-tristate-light.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-tristate-pressed-dark.png b/test/controls/goldens/yaru_checkbox_list_tile-tristate-pressed-dark.png
new file mode 100644
index 000000000..2aa3dd5e9
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-tristate-pressed-dark.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-tristate-pressed-light.png b/test/controls/goldens/yaru_checkbox_list_tile-tristate-pressed-light.png
new file mode 100644
index 000000000..ac3506aa6
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-tristate-pressed-light.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-unchecked-dark.png b/test/controls/goldens/yaru_checkbox_list_tile-unchecked-dark.png
new file mode 100644
index 000000000..9907a630d
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-unchecked-dark.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-unchecked-light.png b/test/controls/goldens/yaru_checkbox_list_tile-unchecked-light.png
new file mode 100644
index 000000000..435faf5ca
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-unchecked-light.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-unckecked-disabled-dark.png b/test/controls/goldens/yaru_checkbox_list_tile-unckecked-disabled-dark.png
new file mode 100644
index 000000000..a6b143bbd
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-unckecked-disabled-dark.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-unckecked-disabled-light.png b/test/controls/goldens/yaru_checkbox_list_tile-unckecked-disabled-light.png
new file mode 100644
index 000000000..4614302a0
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-unckecked-disabled-light.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-unckecked-focused-dark.png b/test/controls/goldens/yaru_checkbox_list_tile-unckecked-focused-dark.png
new file mode 100644
index 000000000..eb249f37b
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-unckecked-focused-dark.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-unckecked-focused-light.png b/test/controls/goldens/yaru_checkbox_list_tile-unckecked-focused-light.png
new file mode 100644
index 000000000..e75515c67
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-unckecked-focused-light.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-unckecked-hovered-dark.png b/test/controls/goldens/yaru_checkbox_list_tile-unckecked-hovered-dark.png
new file mode 100644
index 000000000..0d603f25c
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-unckecked-hovered-dark.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-unckecked-hovered-light.png b/test/controls/goldens/yaru_checkbox_list_tile-unckecked-hovered-light.png
new file mode 100644
index 000000000..403e832b3
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-unckecked-hovered-light.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-unckecked-pressed-dark.png b/test/controls/goldens/yaru_checkbox_list_tile-unckecked-pressed-dark.png
new file mode 100644
index 000000000..89b9f37ff
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-unckecked-pressed-dark.png differ
diff --git a/test/controls/goldens/yaru_checkbox_list_tile-unckecked-pressed-light.png b/test/controls/goldens/yaru_checkbox_list_tile-unckecked-pressed-light.png
new file mode 100644
index 000000000..ed177c6f9
Binary files /dev/null and b/test/controls/goldens/yaru_checkbox_list_tile-unckecked-pressed-light.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-checked-dark.png b/test/controls/goldens/yaru_radio_list_tile-checked-dark.png
new file mode 100644
index 000000000..a71196437
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-checked-dark.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-checked-disabled-dark.png b/test/controls/goldens/yaru_radio_list_tile-checked-disabled-dark.png
new file mode 100644
index 000000000..b2dc6460f
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-checked-disabled-dark.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-checked-disabled-light.png b/test/controls/goldens/yaru_radio_list_tile-checked-disabled-light.png
new file mode 100644
index 000000000..0438d5fa4
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-checked-disabled-light.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-checked-focused-dark.png b/test/controls/goldens/yaru_radio_list_tile-checked-focused-dark.png
new file mode 100644
index 000000000..53dee40be
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-checked-focused-dark.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-checked-focused-light.png b/test/controls/goldens/yaru_radio_list_tile-checked-focused-light.png
new file mode 100644
index 000000000..e73c5729a
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-checked-focused-light.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-checked-hovered-dark.png b/test/controls/goldens/yaru_radio_list_tile-checked-hovered-dark.png
new file mode 100644
index 000000000..ec56d4532
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-checked-hovered-dark.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-checked-hovered-light.png b/test/controls/goldens/yaru_radio_list_tile-checked-hovered-light.png
new file mode 100644
index 000000000..8d74f59df
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-checked-hovered-light.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-checked-light.png b/test/controls/goldens/yaru_radio_list_tile-checked-light.png
new file mode 100644
index 000000000..015626d2e
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-checked-light.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-checked-pressed-dark.png b/test/controls/goldens/yaru_radio_list_tile-checked-pressed-dark.png
new file mode 100644
index 000000000..789a420e7
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-checked-pressed-dark.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-checked-pressed-light.png b/test/controls/goldens/yaru_radio_list_tile-checked-pressed-light.png
new file mode 100644
index 000000000..23a7063e5
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-checked-pressed-light.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-unchecked-dark.png b/test/controls/goldens/yaru_radio_list_tile-unchecked-dark.png
new file mode 100644
index 000000000..d1c47c7c9
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-unchecked-dark.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-unchecked-light.png b/test/controls/goldens/yaru_radio_list_tile-unchecked-light.png
new file mode 100644
index 000000000..31ea04cef
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-unchecked-light.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-unckecked-disabled-dark.png b/test/controls/goldens/yaru_radio_list_tile-unckecked-disabled-dark.png
new file mode 100644
index 000000000..7069f5ea1
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-unckecked-disabled-dark.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-unckecked-disabled-light.png b/test/controls/goldens/yaru_radio_list_tile-unckecked-disabled-light.png
new file mode 100644
index 000000000..e19b447d8
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-unckecked-disabled-light.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-unckecked-focused-dark.png b/test/controls/goldens/yaru_radio_list_tile-unckecked-focused-dark.png
new file mode 100644
index 000000000..7ff80de23
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-unckecked-focused-dark.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-unckecked-focused-light.png b/test/controls/goldens/yaru_radio_list_tile-unckecked-focused-light.png
new file mode 100644
index 000000000..2b8b797fc
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-unckecked-focused-light.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-unckecked-hovered-dark.png b/test/controls/goldens/yaru_radio_list_tile-unckecked-hovered-dark.png
new file mode 100644
index 000000000..3911c9953
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-unckecked-hovered-dark.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-unckecked-hovered-light.png b/test/controls/goldens/yaru_radio_list_tile-unckecked-hovered-light.png
new file mode 100644
index 000000000..20d3fdac8
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-unckecked-hovered-light.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-unckecked-pressed-dark.png b/test/controls/goldens/yaru_radio_list_tile-unckecked-pressed-dark.png
new file mode 100644
index 000000000..96187b725
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-unckecked-pressed-dark.png differ
diff --git a/test/controls/goldens/yaru_radio_list_tile-unckecked-pressed-light.png b/test/controls/goldens/yaru_radio_list_tile-unckecked-pressed-light.png
new file mode 100644
index 000000000..eb635029f
Binary files /dev/null and b/test/controls/goldens/yaru_radio_list_tile-unckecked-pressed-light.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-off-dark.png b/test/controls/goldens/yaru_switch_list_tile-off-dark.png
new file mode 100644
index 000000000..05d0c9ebf
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-off-dark.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-off-disabled-dark.png b/test/controls/goldens/yaru_switch_list_tile-off-disabled-dark.png
new file mode 100644
index 000000000..9ae02b14f
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-off-disabled-dark.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-off-disabled-light.png b/test/controls/goldens/yaru_switch_list_tile-off-disabled-light.png
new file mode 100644
index 000000000..53ec87d90
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-off-disabled-light.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-off-focused-dark.png b/test/controls/goldens/yaru_switch_list_tile-off-focused-dark.png
new file mode 100644
index 000000000..be712209e
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-off-focused-dark.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-off-focused-light.png b/test/controls/goldens/yaru_switch_list_tile-off-focused-light.png
new file mode 100644
index 000000000..4dc9469b8
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-off-focused-light.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-off-hovered-dark.png b/test/controls/goldens/yaru_switch_list_tile-off-hovered-dark.png
new file mode 100644
index 000000000..555a0d267
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-off-hovered-dark.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-off-hovered-light.png b/test/controls/goldens/yaru_switch_list_tile-off-hovered-light.png
new file mode 100644
index 000000000..4a0f1cb4c
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-off-hovered-light.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-off-light.png b/test/controls/goldens/yaru_switch_list_tile-off-light.png
new file mode 100644
index 000000000..333f041a4
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-off-light.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-off-pressed-dark.png b/test/controls/goldens/yaru_switch_list_tile-off-pressed-dark.png
new file mode 100644
index 000000000..3a5f21303
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-off-pressed-dark.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-off-pressed-light.png b/test/controls/goldens/yaru_switch_list_tile-off-pressed-light.png
new file mode 100644
index 000000000..fc0ba84ca
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-off-pressed-light.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-on-dark.png b/test/controls/goldens/yaru_switch_list_tile-on-dark.png
new file mode 100644
index 000000000..eba1ab0e3
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-on-dark.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-on-disabled-dark.png b/test/controls/goldens/yaru_switch_list_tile-on-disabled-dark.png
new file mode 100644
index 000000000..f2ce2620a
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-on-disabled-dark.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-on-disabled-light.png b/test/controls/goldens/yaru_switch_list_tile-on-disabled-light.png
new file mode 100644
index 000000000..1a6aa6eca
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-on-disabled-light.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-on-focused-dark.png b/test/controls/goldens/yaru_switch_list_tile-on-focused-dark.png
new file mode 100644
index 000000000..7288277c3
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-on-focused-dark.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-on-focused-light.png b/test/controls/goldens/yaru_switch_list_tile-on-focused-light.png
new file mode 100644
index 000000000..69e1f2225
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-on-focused-light.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-on-hovered-dark.png b/test/controls/goldens/yaru_switch_list_tile-on-hovered-dark.png
new file mode 100644
index 000000000..a1aa856f1
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-on-hovered-dark.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-on-hovered-light.png b/test/controls/goldens/yaru_switch_list_tile-on-hovered-light.png
new file mode 100644
index 000000000..697f3ccbf
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-on-hovered-light.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-on-light.png b/test/controls/goldens/yaru_switch_list_tile-on-light.png
new file mode 100644
index 000000000..d5d3a76c5
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-on-light.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-on-pressed-dark.png b/test/controls/goldens/yaru_switch_list_tile-on-pressed-dark.png
new file mode 100644
index 000000000..e1a90d7e0
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-on-pressed-dark.png differ
diff --git a/test/controls/goldens/yaru_switch_list_tile-on-pressed-light.png b/test/controls/goldens/yaru_switch_list_tile-on-pressed-light.png
new file mode 100644
index 000000000..7a2af283c
Binary files /dev/null and b/test/controls/goldens/yaru_switch_list_tile-on-pressed-light.png differ
diff --git a/test/controls/yaru_checkbox_list_tile_test.dart b/test/controls/yaru_checkbox_list_tile_test.dart
new file mode 100644
index 000000000..cbab7e15f
--- /dev/null
+++ b/test/controls/yaru_checkbox_list_tile_test.dart
@@ -0,0 +1,161 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:yaru_widgets/yaru_widgets.dart';
+
+import '../yaru_golden_tester.dart';
+
+void main() {
+  testWidgets('contains checkbox, labels and secondary', (tester) async {
+    Widget builder({
+      required Widget title,
+      required Widget? subtitle,
+      required Widget? secondary,
+    }) {
+      return MaterialApp(
+        home: Scaffold(
+          body: YaruCheckboxListTile(
+            title: title,
+            subtitle: subtitle,
+            secondary: secondary,
+            value: false,
+            onChanged: (_) {},
+          ),
+        ),
+      );
+    }
+
+    await tester.pumpWidget(
+      builder(
+        title: const Text('title'),
+        subtitle: null,
+        secondary: null,
+      ),
+    );
+    expect(find.text('title'), findsOneWidget);
+    expect(find.byType(YaruCheckbox), findsOneWidget);
+
+    await tester.pumpWidget(
+      builder(
+        title: const Text('title'),
+        subtitle: const Text('subtitle'),
+        secondary: const Icon(Icons.check_box),
+      ),
+    );
+    expect(find.text('title'), findsOneWidget);
+    expect(find.text('subtitle'), findsOneWidget);
+    expect(find.byIcon(Icons.check_box), findsOneWidget);
+    expect(find.byType(YaruCheckbox), findsOneWidget);
+  });
+
+  testWidgets('the tile react to taps', (tester) async {
+    bool? changedValue;
+    Widget builder({required bool? initialValue, required bool tristate}) {
+      return MaterialApp(
+        home: Scaffold(
+          body: YaruCheckboxListTile(
+            title: const Text('title'),
+            value: initialValue,
+            tristate: tristate,
+            onChanged: (v) => changedValue = v,
+          ),
+        ),
+      );
+    }
+
+    final tileFinder = find.byType(YaruCheckboxListTile);
+
+    await tester.pumpWidget(builder(initialValue: false, tristate: false));
+    await tester.tap(tileFinder);
+    expect(changedValue, isTrue);
+
+    await tester.pumpWidget(builder(initialValue: true, tristate: false));
+    await tester.tap(tileFinder);
+    expect(changedValue, isFalse);
+
+    await tester.pumpWidget(builder(initialValue: false, tristate: true));
+    await tester.tap(tileFinder);
+    expect(changedValue, isTrue);
+
+    await tester.pumpWidget(builder(initialValue: true, tristate: true));
+    await tester.tap(tileFinder);
+    expect(changedValue, null);
+
+    await tester.pumpWidget(builder(initialValue: null, tristate: true));
+    await tester.tap(tileFinder);
+    expect(changedValue, isFalse);
+  });
+
+  testWidgets(
+    'golden images',
+    (tester) async {
+      final variant = goldenVariant.currentValue!;
+
+      // ensure traditional focus highlight
+      FocusManager.instance.highlightStrategy =
+          FocusHighlightStrategy.alwaysTraditional;
+
+      final tristate = variant.label.startsWith('tristate');
+
+      await tester.pumpScaffold(
+        YaruCheckboxListTile(
+          autofocus: variant.hasState(MaterialState.focused),
+          tristate: tristate,
+          value: tristate ? null : variant.hasState(MaterialState.selected),
+          onChanged: variant.hasState(MaterialState.disabled) ? null : (_) {},
+          title: const Text('YaruCheckboxListTile'),
+          subtitle: const Text('Lorem ipsum dolor sit amet'),
+        ),
+        themeMode: variant.themeMode,
+        size: const Size(325, 72),
+      );
+      await tester.pumpAndSettle();
+
+      if (variant.hasState(MaterialState.pressed)) {
+        await tester.down(find.byType(YaruCheckbox));
+        await tester.pumpAndSettle();
+      } else if (variant.hasState(MaterialState.hovered)) {
+        await tester.hover(find.byType(YaruCheckbox));
+        await tester.pumpAndSettle();
+      }
+
+      await expectLater(
+        find.byType(YaruCheckboxListTile),
+        matchesGoldenFile(
+          'goldens/yaru_checkbox_list_tile-${variant.label}.png',
+        ),
+      );
+    },
+    variant: goldenVariant,
+    tags: 'golden',
+  );
+}
+
+final goldenVariant = ValueVariant({
+  ...goldenThemeVariants('unchecked', <MaterialState>{}),
+  ...goldenThemeVariants('unckecked-disabled', {MaterialState.disabled}),
+  ...goldenThemeVariants('unckecked-focused', {MaterialState.focused}),
+  ...goldenThemeVariants('unckecked-hovered', {MaterialState.hovered}),
+  ...goldenThemeVariants('unckecked-pressed', {MaterialState.pressed}),
+  ...goldenThemeVariants('checked', {MaterialState.selected}),
+  ...goldenThemeVariants('checked-disabled', {
+    MaterialState.selected,
+    MaterialState.disabled,
+  }),
+  ...goldenThemeVariants('checked-focused', {
+    MaterialState.selected,
+    MaterialState.focused,
+  }),
+  ...goldenThemeVariants('checked-hovered', {
+    MaterialState.selected,
+    MaterialState.hovered,
+  }),
+  ...goldenThemeVariants('checked-pressed', {
+    MaterialState.selected,
+    MaterialState.pressed,
+  }),
+  ...goldenThemeVariants('tristate', {MaterialState.selected}),
+  ...goldenThemeVariants('tristate-disabled', {MaterialState.disabled}),
+  ...goldenThemeVariants('tristate-focused', {MaterialState.focused}),
+  ...goldenThemeVariants('tristate-hovered', {MaterialState.hovered}),
+  ...goldenThemeVariants('tristate-pressed', {MaterialState.pressed}),
+});
diff --git a/test/controls/yaru_radio_list_tile_test.dart b/test/controls/yaru_radio_list_tile_test.dart
new file mode 100644
index 000000000..615ff1764
--- /dev/null
+++ b/test/controls/yaru_radio_list_tile_test.dart
@@ -0,0 +1,158 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:yaru_widgets/yaru_widgets.dart';
+
+import '../yaru_golden_tester.dart';
+
+void main() {
+  testWidgets('contains radio, labels and secondary', (tester) async {
+    Widget builder({
+      required Widget title,
+      required Widget? subtitle,
+      required Widget? secondary,
+    }) {
+      return MaterialApp(
+        home: Scaffold(
+          body: YaruRadioListTile<int>(
+            title: title,
+            subtitle: subtitle,
+            secondary: secondary,
+            value: 0,
+            groupValue: 0,
+            onChanged: (_) {},
+          ),
+        ),
+      );
+    }
+
+    await tester.pumpWidget(
+      builder(
+        title: const Text('title'),
+        subtitle: null,
+        secondary: null,
+      ),
+    );
+    expect(find.text('title'), findsOneWidget);
+    expect(find.text('subtitle'), findsNothing);
+    expect(find.byType(YaruRadio<int>), findsOneWidget);
+
+    await tester.pumpWidget(
+      builder(
+        title: const Text('title'),
+        subtitle: const Text('subtitle'),
+        secondary: const Icon(Icons.radio_button_checked),
+      ),
+    );
+    expect(find.text('title'), findsOneWidget);
+    expect(find.text('subtitle'), findsOneWidget);
+    expect(find.byIcon(Icons.radio_button_checked), findsOneWidget);
+    expect(find.byType(YaruRadio<int>), findsOneWidget);
+  });
+
+  testWidgets('the tile react to taps', (tester) async {
+    int? changedValue;
+    Widget builder({
+      required int value,
+      required int groupValue,
+      required bool toggleable,
+    }) {
+      return MaterialApp(
+        home: Scaffold(
+          body: YaruRadioListTile<int>(
+            title: const Text('title'),
+            subtitle: const Text('subtitle'),
+            value: value,
+            groupValue: groupValue,
+            toggleable: toggleable,
+            onChanged: (v) => changedValue = v,
+          ),
+        ),
+      );
+    }
+
+    final tileFinder = find.byType(YaruRadioListTile<int>);
+
+    await tester
+        .pumpWidget(builder(value: 1, groupValue: 1, toggleable: false));
+    await tester.tap(tileFinder);
+    expect(changedValue, equals(1));
+
+    await tester
+        .pumpWidget(builder(value: 2, groupValue: 3, toggleable: false));
+    await tester.tap(tileFinder);
+    expect(changedValue, equals(2));
+
+    await tester.pumpWidget(builder(value: 1, groupValue: 1, toggleable: true));
+    await tester.tap(tileFinder);
+    expect(changedValue, equals(null));
+
+    await tester.pumpWidget(builder(value: 2, groupValue: 3, toggleable: true));
+    await tester.tap(tileFinder);
+    expect(changedValue, equals(2));
+  });
+
+  testWidgets(
+    'golden images',
+    (tester) async {
+      final variant = goldenVariant.currentValue!;
+
+      // ensure traditional focus highlight
+      FocusManager.instance.highlightStrategy =
+          FocusHighlightStrategy.alwaysTraditional;
+
+      await tester.pumpScaffold(
+        YaruRadioListTile<bool>(
+          autofocus: variant.hasState(MaterialState.focused),
+          value: variant.hasState(MaterialState.selected),
+          groupValue: true,
+          onChanged: variant.hasState(MaterialState.disabled) ? null : (_) {},
+          title: const Text('YaruRadioListTile'),
+          subtitle: const Text('Lorem ipsum dolor sit amet'),
+        ),
+        themeMode: variant.themeMode,
+        size: const Size(325, 72),
+      );
+      await tester.pumpAndSettle();
+
+      if (variant.hasState(MaterialState.pressed)) {
+        await tester.down(find.byType(YaruRadio<bool>));
+        await tester.pumpAndSettle();
+      } else if (variant.hasState(MaterialState.hovered)) {
+        await tester.hover(find.byType(YaruRadio<bool>));
+        await tester.pumpAndSettle();
+      }
+
+      await expectLater(
+        find.byType(YaruRadioListTile<bool>),
+        matchesGoldenFile('goldens/yaru_radio_list_tile-${variant.label}.png'),
+      );
+    },
+    variant: goldenVariant,
+    tags: 'golden',
+  );
+}
+
+final goldenVariant = ValueVariant({
+  ...goldenThemeVariants('unchecked', <MaterialState>{}),
+  ...goldenThemeVariants('unckecked-disabled', {MaterialState.disabled}),
+  ...goldenThemeVariants('unckecked-focused', {MaterialState.focused}),
+  ...goldenThemeVariants('unckecked-hovered', {MaterialState.hovered}),
+  ...goldenThemeVariants('unckecked-pressed', {MaterialState.pressed}),
+  ...goldenThemeVariants('checked', {MaterialState.selected}),
+  ...goldenThemeVariants('checked-disabled', {
+    MaterialState.selected,
+    MaterialState.disabled,
+  }),
+  ...goldenThemeVariants('checked-focused', {
+    MaterialState.selected,
+    MaterialState.focused,
+  }),
+  ...goldenThemeVariants('checked-hovered', {
+    MaterialState.selected,
+    MaterialState.hovered,
+  }),
+  ...goldenThemeVariants('checked-pressed', {
+    MaterialState.selected,
+    MaterialState.pressed,
+  }),
+});
diff --git a/test/controls/yaru_switch_list_tile_test.dart b/test/controls/yaru_switch_list_tile_test.dart
new file mode 100644
index 000000000..3f87f3c78
--- /dev/null
+++ b/test/controls/yaru_switch_list_tile_test.dart
@@ -0,0 +1,139 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:yaru_widgets/yaru_widgets.dart';
+
+import '../yaru_golden_tester.dart';
+
+void main() {
+  testWidgets('contains switch, labels and secondary', (tester) async {
+    Widget builder({
+      required Widget title,
+      required Widget? subtitle,
+      required Widget? secondary,
+    }) {
+      return MaterialApp(
+        home: Scaffold(
+          body: YaruSwitchListTile(
+            title: title,
+            subtitle: subtitle,
+            secondary: secondary,
+            value: false,
+            onChanged: (_) {},
+          ),
+        ),
+      );
+    }
+
+    await tester.pumpWidget(
+      builder(
+        title: const Text('title'),
+        subtitle: null,
+        secondary: null,
+      ),
+    );
+    expect(find.text('title'), findsOneWidget);
+    expect(find.byType(YaruSwitch), findsOneWidget);
+
+    await tester.pumpWidget(
+      builder(
+        title: const Text('title'),
+        subtitle: const Text('subtitle'),
+        secondary: const Icon(Icons.circle),
+      ),
+    );
+    expect(find.text('title'), findsOneWidget);
+    expect(find.text('subtitle'), findsOneWidget);
+    expect(find.byIcon(Icons.circle), findsOneWidget);
+    expect(find.byType(YaruSwitch), findsOneWidget);
+  });
+
+  testWidgets('the tile react to taps', (tester) async {
+    bool? changedValue;
+    Widget builder({required bool initialValue}) {
+      return MaterialApp(
+        home: Scaffold(
+          body: YaruSwitchListTile(
+            title: const Text('title'),
+            subtitle: const Text('subtitle'),
+            value: initialValue,
+            onChanged: (v) => changedValue = v,
+          ),
+        ),
+      );
+    }
+
+    final switchFinder = find.byType(YaruSwitchListTile);
+
+    await tester.pumpWidget(builder(initialValue: false));
+    await tester.tap(switchFinder);
+    expect(changedValue, isTrue);
+
+    await tester.pumpWidget(builder(initialValue: true));
+    await tester.tap(switchFinder);
+    expect(changedValue, isFalse);
+  });
+
+  testWidgets(
+    'golden images',
+    (tester) async {
+      final variant = goldenVariant.currentValue!;
+
+      // ensure traditional focus highlight
+      FocusManager.instance.highlightStrategy =
+          FocusHighlightStrategy.alwaysTraditional;
+
+      await tester.pumpScaffold(
+        YaruSwitchListTile(
+          autofocus: variant.hasState(MaterialState.focused),
+          value: variant.hasState(MaterialState.selected),
+          onChanged: variant.hasState(MaterialState.disabled) ? null : (_) {},
+          title: const Text('YaruSwitchListTile'),
+          subtitle: const Text('Lorem ipsum dolor sit amet'),
+        ),
+        themeMode: variant.themeMode,
+        size: const Size(325, 72),
+      );
+      await tester.pumpAndSettle();
+
+      if (variant.hasState(MaterialState.pressed)) {
+        await tester.down(find.byType(YaruSwitch));
+        await tester.pumpAndSettle();
+      } else if (variant.hasState(MaterialState.hovered)) {
+        await tester.hover(find.byType(YaruSwitch));
+        await tester.pumpAndSettle();
+      }
+
+      await expectLater(
+        find.byType(YaruSwitchListTile),
+        matchesGoldenFile('goldens/yaru_switch_list_tile-${variant.label}.png'),
+      );
+    },
+    variant: goldenVariant,
+    tags: 'golden',
+  );
+}
+
+final goldenVariant = ValueVariant({
+  ...goldenThemeVariants('off', <MaterialState>{}),
+  ...goldenThemeVariants('off-disabled', {MaterialState.disabled}),
+  ...goldenThemeVariants('off-focused', {MaterialState.focused}),
+  ...goldenThemeVariants('off-hovered', {MaterialState.hovered}),
+  ...goldenThemeVariants('off-pressed', {MaterialState.pressed}),
+  ...goldenThemeVariants('on', {MaterialState.selected}),
+  ...goldenThemeVariants('on-disabled', {
+    MaterialState.selected,
+    MaterialState.disabled,
+  }),
+  ...goldenThemeVariants('on-focused', {
+    MaterialState.selected,
+    MaterialState.focused,
+  }),
+  ...goldenThemeVariants('on-hovered', {
+    MaterialState.selected,
+    MaterialState.hovered,
+  }),
+  ...goldenThemeVariants('on-pressed', {
+    MaterialState.selected,
+    MaterialState.pressed,
+  }),
+});