Checkbox is a Flutter Material widget that renders a square, tappable control for representing a boolean choice. It is commonly used in multi-select lists, settings, preferences, and terms-of-service agreements.
By default, a Checkbox has two states: checked and unchecked. It can also support a third, indeterminate state when tristate is set to true, represented by a dash instead of a checkmark or an empty box. For lists where each row has a checkbox and a corresponding label, CheckboxListTile provides a convenient combined widget.
Checkbox is part of Flutter's core Material library and requires no additional package.tristate: true allows value to be null, which is displayed as a dash. Tapping the checkbox cycles through false → true → null → false.fillColor takes a WidgetStateProperty<Color?>, not a plain Color, so it can vary by state.CheckboxListTile combines a Checkbox with a label, subtitle, and secondary widget in a single list-tile layout.tristate: true, value must not be null; otherwise, Flutter triggers an assertion failure.A Checkbox needs a value and an onChanged callback. In this example, the checkbox state is stored in the parent StatefulWidget.
import 'package:flutter/material.dart';
class TermsCheckbox extends StatefulWidget {
const TermsCheckbox({super.key});
@override
State<TermsCheckbox> createState() => _TermsCheckboxState();
}
class _TermsCheckboxState extends State<TermsCheckbox> {
bool _accepted = false;
@override
Widget build(BuildContext context) {
return Checkbox(
value: _accepted,
onChanged: (value) => setState(() => _accepted = value ?? false),
);
}
}The value ?? false fallback only matters if tristate is ever turned on later – with the default tristate: false, value is never actually null.
CheckboxListTile pairs the checkbox with a label so each list row is a single tappable widget, not a Checkbox plus a separate Text wired together by hand.
import 'package:flutter/material.dart';
class TopicPicker extends StatefulWidget {
const TopicPicker({super.key});
@override
State<TopicPicker> createState() => _TopicPickerState();
}
class _TopicPickerState extends State<TopicPicker> {
final Map<String, bool> _topics = {'Sports': false, 'Tech': true, 'Music': false};
@override
Widget build(BuildContext context) {
return Column(
children: _topics.keys.map((topic) {
return CheckboxListTile(
title: Text(topic),
value: _topics[topic],
onChanged: (value) => setState(() => _topics[topic] = value ?? false),
);
}).toList(),
);
}
}The value ?? false fallback is needed here because the onChanged callback receives a nullable bool (bool?), while _accepted is a non-nullable bool. With the default tristate: false, the callback is normally invoked with either true or false, but its parameter remains nullable in the API because the same callback type also supports tristate checkboxes.
| Checkbox | Switch | Radio |
|---|---|---|
Choice type | ||
| Independent selection | Independent on/off setting | One choice among several |
Multiple selected at once | ||
| Yes | Yes | No – only one per group |
Typical use | ||
| Multi-select lists, agreements | On/off settings and preferences | Single-choice forms |
Indeterminate state | ||
| Yes, via tristate | No | No |
Verdict: use a Checkbox when users can select multiple options independently or need to confirm a choice, such as agreeing to terms. Use a Switch for on/off settings and preferences. Use Radio when the user must choose exactly one option from a group.
CheckboxListTile when a checkbox has a label, rather than wrapping Checkbox and Text in a Row by hand, as long as its Material-style appearance and layout fit your UI. It provides a convenient full-tile interaction target and handles common layout and semantics for you. If you need a custom look or more control over the layout, build the row yourself.tristate only when "unknown" is a real third state, such as a parent checkbox representing a partially-selected group. Don't use it simply as a workaround for a nullable boolean.fillColor, checkColor, and related properties to customize the checkbox's appearance. For state-dependent colors, fillColor accepts a WidgetStateProperty<Color?>, allowing the checkbox to use different colors for states such as selected, hovered, focused, or disabled.Checkbox has an accessible label that describes what it controls. When the label is not provided by surrounding semantics, use semanticLabel or wrap the checkbox in Semantics(label: ...) so assistive technologies can announce its purpose along with its checked state. CheckboxListTile provides the label as part of its overall semantics.Checkbox.adaptive when you want the checkbox to follow the platform's native style without maintaining separate platform-specific widgets.null as value without setting tristate: true. Flutter asserts when a non-tristate checkbox receives a null value. Either keep the value non-null, or use tristate: true when null represents a meaningful third state.tristate as a nullable boolean workaround. A tristate checkbox has different semantics: null represents an indeterminate state, such as a partially selected group. If null does not have a meaningful UI representation, keep the state as a regular bool.Checkbox is controlled by its value. Changing the checkbox visually requires updating the state that supplies value, typically inside setState or another state-management solution. The onChanged callback alone does not change the checkbox's state.
Flutter is known for its seamless UI/UX features and robust design elements. It allowed us to deliver a unique customer experience in the “CA24 Mobile” banking app. This article shares some of the learnings and pain points behind implementing it in this large project.

At LeanCode, while working on complex Flutter projects, we noticed that integrating widgets into Widgetbook was often repetitive and time-consuming. To address this, we created the Widgetbook Entries Generator - a VSCode extension that simplifies the process. See how it works.