Migration to Flutter Guide
Discover our battle-tested 21-step framework for a smooth and successful migration to Flutter!
Home
Glossary

Checkbox in Flutter

What is a checkbox in Flutter?

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.

Key facts at a glance

  • Checkbox is part of Flutter's core Material library and requires no additional package.
  • Setting 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.
  • Without tristate: true, value must not be null; otherwise, Flutter triggers an assertion failure.

How do I add a checkbox in Flutter?

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.

How do I build a checkbox list in Flutter?

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 vs. Switch vs. Radio Button

CheckboxSwitchRadio
Choice type
Independent selectionIndependent on/off settingOne choice among several
Multiple selected at once
YesYesNo – only one per group
Typical use
Multi-select lists, agreementsOn/off settings and preferencesSingle-choice forms
Indeterminate state
Yes, via tristateNoNo

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.

Best practices

  1. Use 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.
  2. Reach for 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.
  3. Use 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.
  4. Make sure a standalone 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.
  5. Consider Checkbox.adaptive when you want the checkbox to follow the platform's native style without maintaining separate platform-specific widgets.
  6. Visually group related checkboxes when they belong to the same set of choices, for example by placing them in a card or separated section.

Common mistakes

  • Passing 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.
  • Treating 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.
  • Forgetting that 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.
  • Omitting an accessible label when using a standalone checkbox. If the surrounding UI does not clearly provide the checkbox's meaning to assistive technologies, add an appropriate semantic label so users know what the checkbox controls.

Learn more

Design system Flutter

Building a Design System in a Large Flutter App

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.

Widgetbook Entries Generator by Leancode

How We Boosted Moving Flutter Widgets to Widgetbook

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.