Checkboxes, radio buttons, and switches all answer "is this on or which one?", and in Flutter they share one pattern: they're controlled widgets that hold no state of their own. You give each a value and an onChanged callback, and you flip the bound variable with setState — miss that and the control simply won't move. This guide covers the shared pattern, single-choice radio groups, the ListTile variants that make tappable rows, tristate checkboxes, and when to choose each, with paste-ready code.

The Complete Flutter Guide: Build Android, iOS and Web apps
Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.
Enrol nowEvery snippet below is paste-ready against current stable Flutter. These selection widgets join the TextField and DropdownButton in your forms toolkit.
We'll wire a checkbox, build a radio group, use the ListTile variants, cover tristate, and settle when to use each control.
If you'd rather watch a settings screen with switches and checkboxes built live, the channel covers these patterns in real apps.
The shared pattern: Checkbox
All three controls work the same way. A Checkbox takes a value from state and an onChanged that updates it — there's no internal state, so the setState is what moves the tick.
bool _agreed = false;
Checkbox(
value: _agreed,
onChanged: (checked) => setState(() => _agreed = checked ?? false),
)
The onChanged argument is nullable because of tristate (covered below), so coalesce to false for a simple two-state box. A Switch is identical — swap Checkbox for Switch and the same code toggles a setting instead.
Single choice: Radio groups
Radio buttons express "pick exactly one". Each Radio has its own value and shares a groupValue; the one whose value equals the group value is selected. In each onChanged you set the group value, which deselects the rest automatically.
enum Plan { free, pro, team }
Plan _plan = Plan.free;
Column(
children: [
for (final plan in Plan.values)
Radio<Plan>(
value: plan,
groupValue: _plan,
onChanged: (value) => setState(() => _plan = value!),
),
],
)
You never have to clear the other options — setting groupValue to the tapped value does it. An enum makes a clean, type-safe set of choices, as covered in the Dart language guide.

Build settings and forms that feel native
The Complete Flutter Guide covers every input widget and full forms inside real, shipped apps.
Enrol nowTappable rows: the ListTile variants
A bare control with a separate label has a small tap target and awkward spacing. CheckboxListTile, RadioListTile, and SwitchListTile bundle the control with a title and subtitle into a full-width, tappable row — exactly what a settings screen wants.
SwitchListTile(
title: const Text('Push notifications'),
subtitle: const Text('Get notified about new courses'),
value: _notify,
onChanged: (on) => setState(() => _notify = on),
)
Now the whole row toggles, and the layout matches the ListTile rows around it. Prefer these variants over a raw control plus a Text whenever the control belongs in a list.
Tristate checkboxes
Set tristate: true and a Checkbox cycles through three states — checked, unchecked, and null (indeterminate). The indeterminate state is the classic "some children selected" marker on a parent checkbox above a group of child boxes. Keep the value nullable to use it, and leave tristate off for ordinary yes/no boxes.
Which control to use
Use a Checkbox to select items from a list or to agree to terms — choices that are part of a form to submit. Use Radio when exactly one option of a small set must be chosen. Use a Switch for a setting that takes effect immediately, like enabling notifications. The intent differs even though the mechanics are the same.
Common mistakes
- Forgetting setState in onChanged. Controlled widgets don't move without it.
- Passing onChanged: null unintentionally. A null callback disables and greys out the control.
- Manually clearing other radios. Setting
groupValuedeselects the rest for you. - Raw controls in a list. Use the
ListTilevariants for a full-row tap target. - A Switch where a Checkbox is meant. Switches imply an immediate effect; checkboxes imply a form choice.
Frequently asked questions
How do I use a Checkbox in Flutter?
Give it a value from a bool and an onChanged that updates that bool with setState; it holds no state itself. Use CheckboxListTile for a tappable row.
How do radio buttons work in Flutter?
Each Radio has a value and shares a groupValue; set the group value in onChanged and the others deselect automatically.
What is the difference between Checkbox and Switch in Flutter?
Same pattern, different intent — a Checkbox is a form choice; a Switch toggles a setting that takes effect immediately. Use SwitchListTile for settings rows.
Why does my Flutter Checkbox not toggle when tapped?
You didn't setState the bound value in onChanged, or onChanged is null (which disables it). Update the bound variable with setState.
Further reads
Keep going with the tutorials that pair with this guide:
- Flutter Development Guide 2026 — the full Flutter hub.
- Flutter DropdownButton — the select-menu input.
- Flutter TextField and TextEditingController — text input and focus.
- Flutter Card and ListTile — the rows the ListTile variants build on.
- Flutter Slider and RangeSlider — the other controlled value pickers.
Sources: Flutter documentation — Checkbox, Radio, Switch, CheckboxListTile, RadioListTile, and SwitchListTile API references (docs.flutter.dev). Verified against current stable Flutter.