Checkbox in Flutter

Checkbox in Flutter: Checkbox & CheckboxListTile Tutorial (2026) | D.Tech Academic

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 (or null) that represents whether the box is checked. true means checked, false means unchecked, and null means "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.

Flutter Checkbox and CheckboxListTile widgets with terms and conditions
Checkbox widgets in a Flutter form
Flutter checkbox checked state with active colour and check colour
Checkbox in checked state

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, or CircleBorder() 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 true to 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 with ListTileControlAffinity.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 SegmentedButton instead.
  • 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 CheckboxListTile handles 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: