Checkbox in Flutter: Checkbox & CheckboxListTile Tutorial (2026)
Last Updated: | Author: Deep Singh
Checkboxes are how users say "yes" to something — accepting terms and conditions, picking multiple items from a list, or toggling a setting on and off. Unlike radio buttons, which let you pick exactly one option, checkboxes let you pick any number.
In this tutorial, you'll learn how to build checkboxes in Flutter two ways: using the bare Checkbox widget and using CheckboxListTile, which packs a checkbox and a label into a nice row. We'll also wire the checkbox up to a submit button so it only becomes enabled after the user accepts the terms.
How Checkboxes Work in Flutter
A checkbox is simpler than a radio button. It has two essential properties:
- value — a
bool(ornull) that represents whether the box is checked.truemeans checked,falsemeans unchecked, andnullmeans "undetermined" (used for tri-state checkboxes). - onChanged — a callback that fires when the user taps the checkbox. You receive the new value and update your state.
The same pattern applies whether you use the bare Checkbox or the full-width CheckboxListTile. The only difference is layout — the ListTile variant handles the row, label, and tap-anywhere-on-the-row behavior for you.
The Full Code
Here's a complete working example. It shows both a bare Checkbox and a CheckboxListTile, and it enables a submit button only after the user accepts the terms.
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Checkbox In Flutter',
home: MyCheckboxPage(),
));
}
class MyCheckboxPage extends StatefulWidget {
const MyCheckboxPage({super.key});
@override
State<MyCheckboxPage> createState() => _MyCheckboxPageState();
}
class _MyCheckboxPageState extends State<MyCheckboxPage> {
bool _acceptedTerms = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Checkbox Tutorial'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ---------- Approach 1: Checkbox + Text ----------
const Text(
'Checkbox With Text',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
const SizedBox(height: 10),
Row(
children: [
Checkbox(
value: _acceptedTerms,
activeColor: Colors.blue,
checkColor: Colors.white,
onChanged: _onTermsChanged,
),
const Expanded(
child: Text('I accept all terms and conditions.'),
),
],
),
const Divider(height: 40, thickness: 2),
// ---------- Approach 2: CheckboxListTile ----------
const Text(
'Checkbox With ListTile',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
const SizedBox(height: 10),
CheckboxListTile(
title: const Text('I accept all terms and conditions.'),
subtitle: const Text('You must accept to continue.'),
secondary: const Icon(Icons.emoji_emotions),
value: _acceptedTerms,
onChanged: _onTermsChanged,
),
const SizedBox(height: 20),
// ---------- Submit button (enabled only when accepted) ----------
Center(
child: ElevatedButton(
onPressed: _acceptedTerms
? () {
debugPrint('Terms accepted — submitting.');
}
: null,
child: const Text('Submit'),
),
),
],
),
),
);
}
void _onTermsChanged(bool? value) {
if (value == null) return;
setState(() {
_acceptedTerms = value;
});
}
}
How the Code Works
Let's walk through the important parts:
1. The State Variable
bool _acceptedTerms = false;
This single boolean controls both checkbox widgets. They both read from it and both write back to it, so they stay in sync — tapping one updates the other.
2. The onChanged Callback
void _onTermsChanged(bool? value) {
if (value == null) return;
setState(() {
_acceptedTerms = value;
});
}
Both checkboxes share this callback. It receives the new value, ignores the null case (which only happens for tri-state checkboxes), and calls setState() to rebuild with the new value.
3. Checkbox + Text
Wrapping the Checkbox and a Text widget in a Row gives you a simple inline layout. The Expanded on the text ensures long labels wrap instead of overflowing.
4. CheckboxListTile
CheckboxListTile is the full-width version. It handles the row, label, optional subtitle, and an optional leading secondary widget (in this case, an icon). Tapping anywhere on the row toggles the checkbox, which is a nice UX improvement over the bare Checkbox.
5. Enabling the Submit Button
onPressed: _acceptedTerms ? () { ... } : null,
Passing null to onPressed disables the button — Flutter automatically greys it out. This is the cleanest way to gate an action behind a requirement. When _acceptedTerms is false, the button is disabled. When the user checks the box, setState() rebuilds, the button receives a real callback, and it becomes tappable.
Styling Your Checkbox
A few properties worth knowing:
- activeColor — the fill colour when the checkbox is checked. Default is your theme's primary colour.
- checkColor — the colour of the tick mark itself. Default is white.
- shape — lets you round the corners. Pass
RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))for slightly rounded checkboxes, orCircleBorder()for a circular one. - side — controls the border colour and width when unchecked. Useful when your background is dark and the default grey border is hard to see.
- tristate — set to
trueto allow a third "undetermined" state, useful for "select all" checkboxes where some children are checked and some aren't. - controlAffinity (on
CheckboxListTile) — moves the checkbox to the trailing side withListTileControlAffinity.trailing. Handy when you want the label on the left and the box on the right, which is common for settings screens.
A couple of extra tips:
- Don't use checkboxes for mutually exclusive options. If only one option can be selected, use radio buttons or a
SegmentedButtoninstead. - Group related checkboxes. If you're letting users select multiple items from a list, put a heading above the group and make sure the tap targets are large enough (at least 48×48 dp, which
CheckboxListTilehandles for you). - Consider a Switch for a single setting. A single checkbox that turns something on or off often reads better as a
Switch. Checkboxes are best when they're part of a group or an explicit agreement like terms and conditions.
Wrapping Up
You now know how to build checkboxes in Flutter with both Checkbox and CheckboxListTile, and how to enable a button only after the user has accepted the terms. That last pattern — gating an action behind a required checkbox — is one of the most common things you'll build in a real app.
From here, you can combine checkboxes with a Form and validation (see the TextField tutorial), or build multi-select lists where several checkboxes share a group and their states are tracked together.
No comments:
Post a Comment