diff --git a/docs/pages/docs/checkbox.mdx b/docs/pages/docs/checkbox.mdx
new file mode 100644
index 000000000..e9f719bec
--- /dev/null
+++ b/docs/pages/docs/checkbox.mdx
@@ -0,0 +1,107 @@
+import { Tabs } from 'nextra/components';
+import { Widget } from "../../components/widget";
+
+# Checkbox
+A control that allows the user to toggle between checked and not checked. It can also be used in a form.
+
+On touch devices, it is recommended to use a [switch](/docs/switch) instead in most cases.
+
+
+
+
+
+
+ ```dart
+ FCheckBox();
+ ```
+
+
+
+## Usage
+
+### `FCheckbox(...)`
+
+```dart
+FCheckbox(
+ enabled: true,
+ initialValue: true,
+);
+```
+
+## Examples
+
+### Disabled
+
+
+
+
+
+
+ ```dart
+ FCheckbox(
+ enabled: false,
+ );
+ ```
+
+
+
+### Form
+
+
+
+
+
+
+ ```dart
+ class LoginForm extends StatefulWidget {
+ const LoginForm({super.key});
+
+ @override
+ State createState() => _LoginFormState();
+ }
+
+ class _LoginFormState extends State {
+ final GlobalKey _formKey = GlobalKey();
+
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) => Form(
+ key: _formKey,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ FTextField.email(
+ hint: 'janedoe@foruslabs.com',
+ help: const Text(''),
+ validator: (value) => (value?.contains('@') ?? false) ? null : 'Please enter a valid email.',
+ ),
+ const SizedBox(height: 4),
+ FTextField.password(
+ hint: '',
+ help: const Text(''),
+ validator: (value) => 8 <= (value?.length ?? 0) ? null : 'Password must be at least 8 characters long.',
+ ),
+ const SizedBox(height: 4),
+ Row(
+ children: [
+ const FCheckbox(),
+ const SizedBox(width: 7),
+ Text('Remember password?', style: context.theme.typography.sm),
+ ],
+ ),
+ const SizedBox(height: 30),
+ FButton(
+ label: const Text('Login'),
+ onPress: () => _formKey.currentState!.validate(),
+ ),
+ ],
+ ),
+ );
+ }
+ ```
+
+
\ No newline at end of file
diff --git a/docs/pages/docs/text-field.mdx b/docs/pages/docs/text-field.mdx
index 23d7e871b..c2960d4f8 100644
--- a/docs/pages/docs/text-field.mdx
+++ b/docs/pages/docs/text-field.mdx
@@ -1,7 +1,7 @@
import { Tabs } from 'nextra/components';
import { Widget } from '../../components/widget';
-# Text Fields
+# Text Field
A text field lets the user enter text, either with hardware keyboard or with an onscreen keyboard. It can also be used
in a form.
diff --git a/forui/CHANGELOG.md b/forui/CHANGELOG.md
index e24a950ce..773b4dcec 100644
--- a/forui/CHANGELOG.md
+++ b/forui/CHANGELOG.md
@@ -1,17 +1,24 @@
## Next
-### Changes
-* Add `FHeader.nested` widget.
-* Add `FProgress` widget.
+### Additions
+* Add `FCheckbox`.
+* Add `FHeader.nested`.
+* Add `FProgress`.
+
+### Enhancements
* **Breaking** Move `FHeaderStyle` to `FHeaderStyles.rootStyle`.
* **Breaking** Move `FHeaderActionStyle.padding` to `FRootHeaderStyle.actionSpacing`.
-* **Breaking** Suffix style parameters with `Style`, i.e. `FRootHeaderStyle.action` has been renamed to `FRootHeaderStyle.actionStyle`.
-* **Breaking** Raw fields have been removed, wrap strings with the Text() widget. Eg. `Button(label: 'Hello')` or `Button(rawLabel: 'Hello')` should be replaced with `Button(label: Text('Hello'))`.
+* **Breaking** Suffix style parameters with `Style`, i.e. `FRootHeaderStyle.action` has been renamed to
+ `FRootHeaderStyle.actionStyle`.
+* **Breaking** Raw fields have been removed, wrap strings with the Text() widget. E.g. `FButton(label: 'Hello')` or
+ `FButton(rawLabel: 'Hello')` should be replaced with `FButton(label: Text('Hello'))`.
* Change `FTextField` to be usable in `Form`s.
* Change `FTextFieldStyle`'s default vertical content padding from `5` to `15`.
+* Split exports in `forui.dart` into sub-libraries.
+
+### Fixes
* Fix missing `key` parameter in `FTextField` constructors.
* **Breaking** `FButton.prefixIcon` and `FButton.suffixIcon` have been renamed to `FButton.prefix` and `FButton.suffix`.
-* Split exports in `forui.dart` into sub-libraries.
* Fix padding inconsistencies in `FCard` and `FDialog`.
## 0.1.0
diff --git a/forui/lib/src/theme/theme_data.dart b/forui/lib/src/theme/theme_data.dart
index 9a24dbea4..c00815fb9 100644
--- a/forui/lib/src/theme/theme_data.dart
+++ b/forui/lib/src/theme/theme_data.dart
@@ -34,6 +34,9 @@ final class FThemeData with Diagnosticable {
/// The card style.
final FCardStyle cardStyle;
+ /// The checkbox style.
+ final FCheckboxStyle checkBoxStyle;
+
/// The dialog style.
final FDialogStyle dialogStyle;
@@ -69,6 +72,7 @@ final class FThemeData with Diagnosticable {
required this.badgeStyles,
required this.buttonStyles,
required this.cardStyle,
+ required this.checkBoxStyle,
required this.dialogStyle,
required this.headerStyle,
required this.progressStyle,
@@ -96,6 +100,7 @@ final class FThemeData with Diagnosticable {
badgeStyles: FBadgeStyles.inherit(colorScheme: colorScheme, typography: typography, style: style),
buttonStyles: FButtonStyles.inherit(colorScheme: colorScheme, typography: typography, style: style),
cardStyle: FCardStyle.inherit(colorScheme: colorScheme, typography: typography, style: style),
+ checkBoxStyle: FCheckboxStyle.inherit(colorScheme: colorScheme),
dialogStyle: FDialogStyle.inherit(colorScheme: colorScheme, typography: typography, style: style),
headerStyle: FHeaderStyles.inherit(colorScheme: colorScheme, typography: typography, style: style),
progressStyle: FProgressStyle.inherit(colorScheme: colorScheme, style: style),
@@ -131,6 +136,7 @@ final class FThemeData with Diagnosticable {
FBadgeStyles? badgeStyles,
FButtonStyles? buttonStyles,
FCardStyle? cardStyle,
+ FCheckboxStyle? checkBoxStyle,
FDialogStyle? dialogStyle,
FHeaderStyles? headerStyle,
FProgressStyle? progressStyle,
@@ -147,6 +153,7 @@ final class FThemeData with Diagnosticable {
badgeStyles: badgeStyles ?? this.badgeStyles,
buttonStyles: buttonStyles ?? this.buttonStyles,
cardStyle: cardStyle ?? this.cardStyle,
+ checkBoxStyle: checkBoxStyle ?? this.checkBoxStyle,
dialogStyle: dialogStyle ?? this.dialogStyle,
headerStyle: headerStyle ?? this.headerStyle,
progressStyle: progressStyle ?? this.progressStyle,
@@ -167,6 +174,7 @@ final class FThemeData with Diagnosticable {
..add(DiagnosticsProperty('badgeStyles', badgeStyles, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty('buttonStyles', buttonStyles, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty('cardStyle', cardStyle, level: DiagnosticLevel.debug))
+ ..add(DiagnosticsProperty('checkBoxStyle', checkBoxStyle, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty('dialogStyle', dialogStyle, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty('headerStyle', headerStyle, level: DiagnosticLevel.debug))
..add(DiagnosticsProperty('progressStyle', progressStyle))
@@ -188,6 +196,7 @@ final class FThemeData with Diagnosticable {
badgeStyles == other.badgeStyles &&
buttonStyles == other.buttonStyles &&
cardStyle == other.cardStyle &&
+ checkBoxStyle == other.checkBoxStyle &&
dialogStyle == other.dialogStyle &&
headerStyle == other.headerStyle &&
progressStyle == other.progressStyle &&
@@ -205,6 +214,7 @@ final class FThemeData with Diagnosticable {
badgeStyles.hashCode ^
buttonStyles.hashCode ^
cardStyle.hashCode ^
+ checkBoxStyle.hashCode ^
dialogStyle.hashCode ^
headerStyle.hashCode ^
progressStyle.hashCode ^
diff --git a/forui/lib/src/widgets/checkbox.dart b/forui/lib/src/widgets/checkbox.dart
new file mode 100644
index 000000000..953c2815f
--- /dev/null
+++ b/forui/lib/src/widgets/checkbox.dart
@@ -0,0 +1,347 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/semantics.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+
+import 'package:forui/forui.dart';
+
+/// A checkbox control that allows the user to toggle between checked and not checked.
+///
+/// On touch devices, it is recommended to use a [FSwitch] instead in most cases. A [FCheckbox] is internally a
+/// [FormField], therefore it can be used in a form.
+///
+/// See:
+/// * https://forui.dev/docs/checkbox for working examples.
+/// * [FCheckboxStyle] for customizing a checkbox's appearance.
+class FCheckbox extends StatelessWidget {
+ /// The semantic label of the dialog used by accessibility frameworks to announce screen transitions when the dialog
+ /// is opened and closed.
+ ///
+ /// See also:
+ /// * [SemanticsConfiguration.namesRoute], for a description of how this value is used.
+ final String? semanticLabel;
+
+ /// Called when the user initiates a change to the FCheckBox's value: when they have checked or unchecked this box.
+ final ValueChanged? onChange;
+
+ /// Whether this checkbox should focus itself if nothing else is already focused. Defaults to false.
+ final bool autofocus;
+
+ /// Defines the [FocusNode] for this checkbox.
+ final FocusNode? focusNode;
+
+ /// Handler called when the focus changes.
+ ///
+ /// Called with true if this widget's node gains focus, and false if it loses focus.
+ final ValueChanged? onFocusChange;
+
+ /// An optional method to call with the final value when the form is saved via [FormState.save].
+ final FormFieldSetter? onSave;
+
+ /// An optional method that validates an input. Returns an error string to display if the input is invalid, or null
+ /// otherwise.
+ ///
+ /// The returned value is exposed by the [FormFieldState.errorText] property.
+ final FormFieldValidator? validator;
+
+ /// An optional value to initialize the form field to, or null otherwise.
+ final bool initialValue;
+
+ /// Whether the form is able to receive user input.
+ ///
+ /// Defaults to true. If [autovalidateMode] is not [AutovalidateMode.disabled], the field will be auto validated.
+ /// Likewise, if this field is false, the widget will not be validated regardless of [autovalidateMode].
+ final bool enabled;
+
+ /// Used to enable/disable this form field auto validation and update its error text.
+ ///
+ /// Defaults to [AutovalidateMode.disabled].
+ ///
+ /// If [AutovalidateMode.onUserInteraction], this FormField will only auto-validate after its content changes. If
+ /// [AutovalidateMode.always], it will auto-validate even without user interaction. If [AutovalidateMode.disabled],
+ /// auto-validation will be disabled.
+ final AutovalidateMode? autovalidateMode;
+
+ /// Restoration ID to save and restore the state of the form field.
+ ///
+ /// Setting the restoration ID to a non-null value results in whether or not the form field validation persists.
+ ///
+ /// The state of this widget is persisted in a [RestorationBucket] claimed from the surrounding [RestorationScope]
+ /// using the provided restoration ID.
+ ///
+ /// See also:
+ /// * [RestorationManager], which explains how state restoration works in Flutter.
+ final String? restorationId;
+
+ /// Creates a [FCheckbox].
+ const FCheckbox({
+ this.semanticLabel,
+ this.onChange,
+ this.autofocus = false,
+ this.focusNode,
+ this.onFocusChange,
+ this.onSave,
+ this.validator,
+ this.initialValue = false,
+ this.enabled = true,
+ this.autovalidateMode,
+ this.restorationId,
+ super.key,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ final style = context.theme.checkBoxStyle;
+ final stateStyle = enabled ? style.enabledStyle : style.disabledStyle;
+
+ return FocusableActionDetector(
+ autofocus: autofocus,
+ focusNode: focusNode,
+ onFocusChange: onFocusChange,
+ child: MouseRegion(
+ cursor: enabled ? SystemMouseCursors.click : MouseCursor.defer,
+ child: FormField(
+ onSaved: onSave,
+ validator: validator,
+ initialValue: initialValue,
+ enabled: enabled,
+ autovalidateMode: autovalidateMode,
+ restorationId: restorationId,
+ builder: (state) {
+ final value = state.value ?? initialValue;
+ return Semantics(
+ label: semanticLabel,
+ enabled: enabled,
+ checked: value,
+ child: GestureDetector(
+ onTap: enabled
+ ? () {
+ state.didChange(!value);
+ onChange?.call(!value);
+ }
+ : null,
+ child: AnimatedSwitcher(
+ duration: style.animationDuration,
+ switchInCurve: style.curve,
+ child: SizedBox.square(
+ key: ValueKey(value),
+ dimension: 20,
+ child: DecoratedBox(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(4),
+ border: Border.all(
+ color: stateStyle.borderColor,
+ width: 0.6,
+ ),
+ color: value ? stateStyle.checkedBackgroundColor : stateStyle.uncheckedBackgroundColor,
+ ),
+ child: value
+ ? FAssets.icons.check(
+ height: 15,
+ width: 15,
+ colorFilter: ColorFilter.mode(
+ stateStyle.iconColor,
+ BlendMode.srcIn,
+ ),
+ )
+ : const SizedBox(),
+ ),
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ );
+ }
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties
+ ..add(StringProperty('semanticLabel', semanticLabel))
+ ..add(ObjectFlagProperty.has('onChange', onChange))
+ ..add(DiagnosticsProperty('autofocus', autofocus))
+ ..add(DiagnosticsProperty('focusNode', focusNode))
+ ..add(ObjectFlagProperty.has('onFocusChange', onFocusChange))
+ ..add(ObjectFlagProperty.has('onSave', onSave))
+ ..add(ObjectFlagProperty.has('validator', validator))
+ ..add(DiagnosticsProperty('initialValue', initialValue))
+ ..add(DiagnosticsProperty('enabled', enabled))
+ ..add(EnumProperty('autovalidateMode', autovalidateMode))
+ ..add(StringProperty('restorationId', restorationId));
+ }
+}
+
+/// A [FCheckbox]'s style.
+final class FCheckboxStyle with Diagnosticable {
+ /// The duration of the animation when the checkbox's switches between checked and unchecked.
+ ///
+ /// Defaults to `const Duration(milliseconds: 100)`.
+ final Duration animationDuration;
+
+ /// The curve of the animation when the checkbox's switches between checked and unchecked.
+ ///
+ /// Defaults to [Curves.linear].
+ final Curve curve;
+
+ /// The checkbox's style when it's enabled.
+ final FCheckboxStateStyle enabledStyle;
+
+ /// The checkbox's style when it's disabled.
+ final FCheckboxStateStyle disabledStyle;
+
+ /// Creates a [FCheckboxStyle].
+ FCheckboxStyle({
+ required this.enabledStyle,
+ required this.disabledStyle,
+ this.animationDuration = const Duration(milliseconds: 100),
+ this.curve = Curves.linear,
+ });
+
+ /// Creates a [FCheckboxStyle] that inherits its properties from the given [FColorScheme].
+ FCheckboxStyle.inherit({required FColorScheme colorScheme})
+ : animationDuration = const Duration(milliseconds: 100),
+ curve = Curves.linear,
+ enabledStyle = FCheckboxStateStyle(
+ borderColor: colorScheme.primary,
+ iconColor: colorScheme.background,
+ checkedBackgroundColor: colorScheme.primary,
+ uncheckedBackgroundColor: colorScheme.background,
+ ),
+ disabledStyle = FCheckboxStateStyle(
+ borderColor: colorScheme.primary.withOpacity(0.5),
+ iconColor: colorScheme.background.withOpacity(0.5),
+ checkedBackgroundColor: colorScheme.primary.withOpacity(0.5),
+ uncheckedBackgroundColor: colorScheme.background.withOpacity(0.5),
+ );
+
+ /// Returns a copy of this [FCheckboxStyle] with the given properties replaced.
+ ///
+ /// ```dart
+ /// final style = FCheckboxStyle(
+ /// animationDuration: const Duration(minutes: 1),
+ /// curve: Curves.linear,
+ /// // Other arguments omitted for brevity.
+ /// );
+ ///
+ /// final copy = style.copyWith(
+ /// curve: Curves.bounceIn,
+ /// );
+ ///
+ /// print(style.animationDuration); // const Duration(minutes: 1)
+ /// print(copy.curve); // Curves.bounceIn
+ /// ```
+ FCheckboxStyle copyWith({
+ Duration? animationDuration,
+ Curve? curve,
+ FCheckboxStateStyle? enabledStyle,
+ FCheckboxStateStyle? disabledStyle,
+ }) =>
+ FCheckboxStyle(
+ animationDuration: animationDuration ?? this.animationDuration,
+ curve: curve ?? this.curve,
+ enabledStyle: enabledStyle ?? this.enabledStyle,
+ disabledStyle: disabledStyle ?? this.disabledStyle,
+ );
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties
+ ..add(DiagnosticsProperty('animationDuration', animationDuration))
+ ..add(DiagnosticsProperty('curve', curve))
+ ..add(DiagnosticsProperty('enabledStyle', enabledStyle))
+ ..add(DiagnosticsProperty('disabledStyle', disabledStyle));
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is FCheckboxStyle &&
+ runtimeType == other.runtimeType &&
+ animationDuration == other.animationDuration &&
+ curve == other.curve &&
+ enabledStyle == other.enabledStyle &&
+ disabledStyle == other.disabledStyle;
+
+ @override
+ int get hashCode => animationDuration.hashCode ^ curve.hashCode ^ enabledStyle.hashCode ^ disabledStyle.hashCode;
+}
+
+/// A checkbox state's style.
+final class FCheckboxStateStyle with Diagnosticable {
+ /// The checkbox's border color.
+ final Color borderColor;
+
+ /// The checked checkbox's icon's color.
+ final Color iconColor;
+
+ /// The checked checkbox's background color.
+ final Color checkedBackgroundColor;
+
+ /// The unchecked checkbox's background color.
+ final Color uncheckedBackgroundColor;
+
+ /// Creates a [FCheckboxStateStyle].
+ const FCheckboxStateStyle({
+ required this.borderColor,
+ required this.iconColor,
+ required this.checkedBackgroundColor,
+ required this.uncheckedBackgroundColor,
+ });
+
+ /// Returns a copy of this [FCheckboxStateStyle] with the given properties replaced.
+ ///
+ /// ```dart
+ /// final style = FCheckBoxStateStyle(
+ /// iconColor: ...,
+ /// checkedBackgroundColor: ...,
+ /// // Other arguments omitted for brevity.
+ /// );
+ ///
+ /// final copy = style.copyWith(
+ /// checkedBackgroundColor: ...,
+ /// );
+ ///
+ /// print(style.iconColor == copy.iconColor); // true
+ /// print(style.checkedBackgroundColor == copy.checkedBackgroundColor); // false
+ /// ```
+ FCheckboxStateStyle copyWith({
+ Color? borderColor,
+ Color? iconColor,
+ Color? checkedBackgroundColor,
+ Color? uncheckedBackgroundColor,
+ }) =>
+ FCheckboxStateStyle(
+ borderColor: borderColor ?? this.borderColor,
+ iconColor: iconColor ?? this.iconColor,
+ checkedBackgroundColor: checkedBackgroundColor ?? this.checkedBackgroundColor,
+ uncheckedBackgroundColor: uncheckedBackgroundColor ?? this.uncheckedBackgroundColor,
+ );
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties
+ ..add(ColorProperty('borderColor', borderColor))
+ ..add(ColorProperty('checkedIconColor', iconColor))
+ ..add(ColorProperty('checkedBackgroundColor', checkedBackgroundColor))
+ ..add(ColorProperty('uncheckedBackgroundColor', uncheckedBackgroundColor));
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is FCheckboxStateStyle &&
+ runtimeType == other.runtimeType &&
+ borderColor == other.borderColor &&
+ iconColor == other.iconColor &&
+ checkedBackgroundColor == other.checkedBackgroundColor &&
+ uncheckedBackgroundColor == other.uncheckedBackgroundColor;
+
+ @override
+ int get hashCode =>
+ borderColor.hashCode ^ iconColor.hashCode ^ checkedBackgroundColor.hashCode ^ uncheckedBackgroundColor.hashCode;
+}
diff --git a/forui/lib/src/widgets/text_field/text_field.dart b/forui/lib/src/widgets/text_field/text_field.dart
index 31c1c04b1..927ac7f82 100644
--- a/forui/lib/src/widgets/text_field/text_field.dart
+++ b/forui/lib/src/widgets/text_field/text_field.dart
@@ -61,7 +61,7 @@ final class FTextField extends StatelessWidget {
/// Controls the text being edited. If null, this widget will create its own [TextEditingController].
final TextEditingController? controller;
- /// Defines the keyboard focus for this
+ /// Defines the keyboard focus for this [FTextField].
///
/// See [TextField.focusNode] for more information.
final FocusNode? focusNode;
diff --git a/forui/lib/widgets.dart b/forui/lib/widgets.dart
index 2334cd26d..25e657a06 100644
--- a/forui/lib/widgets.dart
+++ b/forui/lib/widgets.dart
@@ -4,6 +4,7 @@ library forui.widgets;
export 'src/widgets/badge/badge.dart' hide Variant;
export 'src/widgets/button/button.dart' hide Variant;
export 'src/widgets/card/card.dart';
+export 'src/widgets/checkbox.dart';
export 'src/widgets/dialog/dialog.dart';
export 'src/widgets/header/header.dart';
export 'src/widgets/progress.dart';
diff --git a/forui/test/golden/check-box/zinc-dark-disabled-value.png b/forui/test/golden/check-box/zinc-dark-disabled-value.png
new file mode 100644
index 000000000..964d8fee6
Binary files /dev/null and b/forui/test/golden/check-box/zinc-dark-disabled-value.png differ
diff --git a/forui/test/golden/check-box/zinc-dark-enabled-no-value.png b/forui/test/golden/check-box/zinc-dark-enabled-no-value.png
new file mode 100644
index 000000000..5e552fb69
Binary files /dev/null and b/forui/test/golden/check-box/zinc-dark-enabled-no-value.png differ
diff --git a/forui/test/golden/check-box/zinc-dark-enabled-value.png b/forui/test/golden/check-box/zinc-dark-enabled-value.png
new file mode 100644
index 000000000..46e4f5510
Binary files /dev/null and b/forui/test/golden/check-box/zinc-dark-enabled-value.png differ
diff --git a/forui/test/golden/check-box/zinc-light-disabled-value.png b/forui/test/golden/check-box/zinc-light-disabled-value.png
new file mode 100644
index 000000000..266d5ea38
Binary files /dev/null and b/forui/test/golden/check-box/zinc-light-disabled-value.png differ
diff --git a/forui/test/golden/check-box/zinc-light-enabled-no-value.png b/forui/test/golden/check-box/zinc-light-enabled-no-value.png
new file mode 100644
index 000000000..5e477dcc5
Binary files /dev/null and b/forui/test/golden/check-box/zinc-light-enabled-no-value.png differ
diff --git a/forui/test/golden/check-box/zinc-light-enabled-value.png b/forui/test/golden/check-box/zinc-light-enabled-value.png
new file mode 100644
index 000000000..82a23e217
Binary files /dev/null and b/forui/test/golden/check-box/zinc-light-enabled-value.png differ
diff --git a/forui/test/src/widgets/checkbox_golden_test.dart b/forui/test/src/widgets/checkbox_golden_test.dart
new file mode 100644
index 000000000..522fe0678
--- /dev/null
+++ b/forui/test/src/widgets/checkbox_golden_test.dart
@@ -0,0 +1,41 @@
+@Tags(['golden'])
+library;
+
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:forui/forui.dart';
+import '../test_scaffold.dart';
+
+void main() {
+ group('FCheckBox', () {
+ for (final (name, theme, background) in TestScaffold.themes) {
+ for (final (enabled, initialValue) in [
+ (true, true),
+ (true, false),
+ (false, true),
+ (true, true),
+ ]) {
+ testWidgets('$name with ${enabled ? 'enabled' : 'disabled'} & ${initialValue ? 'value' : 'no-value'}',
+ (tester) async {
+ await tester.pumpWidget(
+ TestScaffold(
+ data: theme,
+ background: background,
+ child: FCheckbox(
+ enabled: enabled,
+ initialValue: initialValue,
+ ),
+ ),
+ );
+
+ await expectLater(
+ find.byType(TestScaffold),
+ matchesGoldenFile(
+ 'check-box/$name-${enabled ? 'enabled' : 'disabled'}-${initialValue ? 'value' : 'no-value'}.png',
+ ),
+ );
+ });
+ }
+ }
+ });
+}
diff --git a/samples/lib/main.dart b/samples/lib/main.dart
index 90305cc4c..8896f9170 100644
--- a/samples/lib/main.dart
+++ b/samples/lib/main.dart
@@ -55,6 +55,14 @@ class _AppRouter extends $_AppRouter {
path: '/card/default',
page: CardRoute.page,
),
+ AutoRoute(
+ path: '/checkbox/default',
+ page: CheckboxRoute.page,
+ ),
+ AutoRoute(
+ path: '/checkbox/form',
+ page: FormCheckboxRoute.page,
+ ),
AutoRoute(
path: '/dialog/default',
page: DialogRoute.page,
diff --git a/samples/lib/widgets/checkbox.dart b/samples/lib/widgets/checkbox.dart
new file mode 100644
index 000000000..b3a46b631
--- /dev/null
+++ b/samples/lib/widgets/checkbox.dart
@@ -0,0 +1,92 @@
+import 'package:flutter/widgets.dart';
+
+import 'package:auto_route/auto_route.dart';
+import 'package:forui/forui.dart';
+
+import 'package:forui_samples/sample_scaffold.dart';
+
+@RoutePage()
+class CheckboxPage extends SampleScaffold {
+ final bool enabled;
+
+ CheckboxPage({
+ @queryParam super.theme,
+ @queryParam this.enabled = false,
+ });
+
+ @override
+ Widget child(BuildContext context) => Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
+ child: FCheckbox(
+ enabled: enabled,
+ ),
+ ),
+ ],
+ );
+}
+
+@RoutePage()
+class FormCheckboxPage extends SampleScaffold {
+ FormCheckboxPage({
+ @queryParam super.theme,
+ });
+
+ @override
+ Widget child(BuildContext context) => const Padding(
+ padding: EdgeInsets.all(15.0),
+ child: LoginForm(),
+ );
+}
+
+class LoginForm extends StatefulWidget {
+ const LoginForm({super.key});
+
+ @override
+ State createState() => _LoginFormState();
+}
+
+class _LoginFormState extends State {
+ final GlobalKey _formKey = GlobalKey();
+
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) => Form(
+ key: _formKey,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ FTextField.email(
+ hint: 'janedoe@foruslabs.com',
+ help: const Text(''),
+ validator: (value) => (value?.contains('@') ?? false) ? null : 'Please enter a valid email.',
+ ),
+ const SizedBox(height: 4),
+ FTextField.password(
+ hint: '',
+ help: const Text(''),
+ validator: (value) => 8 <= (value?.length ?? 0) ? null : 'Password must be at least 8 characters long.',
+ ),
+ const SizedBox(height: 4),
+ Row(
+ children: [
+ const FCheckbox(),
+ const SizedBox(width: 7),
+ Text('Remember password?', style: context.theme.typography.sm),
+ ],
+ ),
+ const SizedBox(height: 30),
+ FButton(
+ label: const Text('Login'),
+ onPress: () => _formKey.currentState!.validate(),
+ ),
+ ],
+ ),
+ );
+}