Flutter Checkbox, Radio, and Switch: Selection Widgets Tutorial

Coding Liquids blog cover featuring Sagnik Bhattacharya for the Flutter Checkbox, Radio, and Switch guide, with a checked box, a selected radio group, and a toggled switch on tappable list rows.
Coding Liquids blog cover featuring Sagnik Bhattacharya for the Flutter Checkbox, Radio, and Switch guide.

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 course thumbnail

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 now

Every snippet below is paste-ready against current stable Flutter. These selection widgets join the TextField and DropdownButton in your forms toolkit.

Follow me on Instagram@sagnikteaches

We'll wire a checkbox, build a radio group, use the ListTile variants, cover tristate, and settle when to use each control.

Connect on LinkedInSagnik Bhattacharya

If you'd rather watch a settings screen with switches and checkboxes built live, the channel covers these patterns in real apps.

Subscribe on YouTube@codingliquids

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.

The Complete Flutter Guide course thumbnail

Build settings and forms that feel native

The Complete Flutter Guide covers every input widget and full forms inside real, shipped apps.

Enrol now

Tappable 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 groupValue deselects the rest for you.
  • Raw controls in a list. Use the ListTile variants 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:

Sources: Flutter documentation — Checkbox, Radio, Switch, CheckboxListTile, RadioListTile, and SwitchListTile API references (docs.flutter.dev). Verified against current stable Flutter.