Flutter Lesson 30 of 83 4 min read
Checkbox, Switch, Radio and Slider in Flutter
Learn Flutter selection controls: Checkbox, Switch, radio groups, SegmentedButton and Slider, with list tile versions and state handling.
On this page
These controls let the user choose without typing. They all follow one pattern: you give the control its current value and an onChanged callback, and you update the state yourself.
All of them together #
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: SettingsPage()));
enum ShirtSize { small, medium, large }
class SettingsPage extends StatefulWidget {
const SettingsPage({super.key});
@override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
bool _agree = false;
bool _notifications = true;
ShirtSize _size = ShirtSize.medium;
double _volume = 40;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Controls')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
CheckboxListTile(
title: const Text('I agree to the terms'),
value: _agree,
onChanged: (v) => setState(() => _agree = v ?? false),
),
SwitchListTile(
title: const Text('Notifications'),
value: _notifications,
onChanged: (v) => setState(() => _notifications = v),
),
const SizedBox(height: 16),
SegmentedButton<ShirtSize>(
segments: const [
ButtonSegment(value: ShirtSize.small, label: Text('Small')),
ButtonSegment(value: ShirtSize.medium, label: Text('Medium')),
ButtonSegment(value: ShirtSize.large, label: Text('Large')),
],
selected: {_size},
onSelectionChanged: (s) => setState(() => _size = s.first),
),
const SizedBox(height: 16),
Slider(
value: _volume,
min: 0,
max: 100,
divisions: 10,
label: '${_volume.round()}',
onChanged: (v) => setState(() => _volume = v),
),
Text('Agree: $_agree, notifications: $_notifications, '
'size: ${_size.name}, volume: ${_volume.round()}'),
],
),
);
}
}
Which control for which choice? #
| The user is… | Use |
|---|---|
| Agreeing, or ticking several items in a list | Checkbox |
| Turning a setting on or off, effective at once | Switch |
| Choosing one of two to five options, all visible | SegmentedButton or radio buttons |
| Choosing one of many options | A dropdown |
| Choosing a number in a range | Slider |
Checkbox #
Checkbox(
value: _checked,
onChanged: (bool? v) => setState(() => _checked = v ?? false),
)
The value is bool? because a checkbox can have a third “partly selected” state when tristate: true. Prefer CheckboxListTile: the whole row becomes tappable.
Switch #
Switch(value: _on, onChanged: (v) => setState(() => _on = v))
Switch.adaptive(value: _on, onChanged: (v) => setState(() => _on = v)) // iOS look on iOS
Radio buttons #
A radio group picks exactly one value. Wrap the options in a RadioGroup, which holds the selected value.
enum Delivery { standard, express, pickup }
Delivery _delivery = Delivery.standard;
RadioGroup<Delivery>(
groupValue: _delivery,
onChanged: (v) => setState(() => _delivery = v ?? _delivery),
child: const Column(
children: [
RadioListTile(value: Delivery.standard, title: Text('Standard (3 to 5 days)')),
RadioListTile(value: Delivery.express, title: Text('Express (next day)')),
RadioListTile(value: Delivery.pickup, title: Text('Pick up in store')),
],
),
)
In Flutter versions before RadioGroup existed, each RadioListTile took groupValue and onChanged itself. You will see that form in older code.
SegmentedButton #
A compact alternative to radio buttons. Add multiSelectionEnabled: true to allow several choices, in which case selected holds a set of them.
Slider #
Slider(
value: _brightness, // must lie between min and max
min: 0,
max: 1,
onChanged: (v) => setState(() => _brightness = v),
onChangeEnd: (v) => _save(v), // called once when the user lets go
)
RangeSlider(
values: _priceRange, // a RangeValues(100, 500)
min: 0,
max: 1000,
divisions: 20,
labels: RangeLabels('${_priceRange.start.round()}', '${_priceRange.end.round()}'),
onChanged: (v) => setState(() => _priceRange = v),
)
Do expensive work, such as saving or a network call, in onChangeEnd, not in onChanged, which fires continuously while dragging.
Disabling a control #
Pass null as onChanged. The control greys out.
Switch(value: _on, onChanged: _canEdit ? (v) => setState(() => _on = v) : null)
A list of checkboxes from data #
final _toppings = <String, bool>{'Cheese': true, 'Olives': false, 'Mushroom': false};
Column(
children: [
for (final entry in _toppings.entries)
CheckboxListTile(
title: Text(entry.key),
value: entry.value,
onChanged: (v) => setState(() => _toppings[entry.key] = v ?? false),
),
],
)
Try it yourself #
Build a pizza order screen: a segmented button for size, a list of topping checkboxes, a slider for quantity from 1 to 10, and a switch for “extra spicy”. Show a summary line and a total price that update as the choices change.