Flutter - Using SegmentedButton Widget Examples

This tutorial shows you how to use the SegmentedButton widget in Flutter.

Segmented button is a Material Design component that allows users to select options, switch views, or sort elements. Common usages include displaying product options, filter options, and tab switcher. Flutter already provides a widget called SegmentedButton. By using the widget, you can easily implement Material Design's segmented button. Below are the usage examples including how to customize the behavior and appearance.

Using SegmentedButton

To use the SegmentedButton widget, you need to call the constructor. The constructor has two required named arguments, segments and selected.

For the segments argument, you have to pass a List of ButtonSegments. Each ButtonSegment represents a segment that can be selected. The ButtonSegment itself has a type parameter where you need to specify the type of the values that can be selected.

To create a ButtonSegment, call the constructor. For the value argument, you need to pass a value that complies with the type parameter. To display a text in the button, you can pass the label argument. It's also possible to set an icon by passing a widget as the icon argument. For each button, you can choose to pass label, icon, or both. To control whether a segment is selectable, you can set the value of the enabled argument which defaults to true.

For the selected argument of SegmentedButton, you need to pass a Set whose element type is the same as the type parameter. It contains the currently selected values. The ButtonSegment can be configured to allow multiple selections using the multiSelectionEnabled argument which will be exemplified later. Below is a basic example that supports single selection and only passes the required arguments.

  enum Color { red, green, blue, yellow }
  
  class ColorOptions extends StatefulWidget {
    const ColorOptions({super.key});
  
    @override
    State<ColorOptions> createState() => _ColorOptionsState();
  }
  
  class _ColorOptionsState extends State<ColorOptions> {
    Color _selectedColor = Color.blue;
  
    @override
    Widget build(BuildContext context) {
      return SegmentedButton<Color>(
        segments: const <ButtonSegment<Color>>[
          ButtonSegment<Color>(
              value: Color.red,
              label: Text('Red'),
          ),
          ButtonSegment<Color>(
              value: Color.green,
              label: Text('Green'),
          ),
          ButtonSegment<Color>(
              value: Color.blue,
              label: Text('Blue'),
          ),
          ButtonSegment<Color>(
              value: Color.yellow,
              label: Text('Yellow'),
          ),
        ],
        selected: <Color>{_selectedColor},
      );
    }
  }

Output:

Flutter - SegmentedButton

If you see the screenshot above, the buttons are disabled. That's because the onSelectionChanged argument is null. Despite not being required, the onSelectionChanged argument should be passed in almost all cases. I am going to explain it in the next section.

Handle Selection Change

Flutter will consider a segment is being selected based on the value of the selected argument. When the user selects or unselects a segment, the Set passed to the selected argument may need to be updated. Flutter doesn't do that automatically, so it's your responsibility to update the value.

To handle selection change, pass a function as the onSelectionChanged argument. When the selection changes, the function will be triggered and you can get the latest selection from the argument passed to the function. Inside the function, you have to update the selected value. A common implementation is to store the value in a state variable, so that it can be updated using setState when the onSelectionChanged function is called.

  SegmentedButton<Accessory>(
    // other arguments
    onSelectionChanged: (Set<Color> newSelection) {
      setState(() {
        _selectedColor = newSelection.first;
      });
    },
  )

Output:

Flutter - SegmentedButton - Handle Selection Change

Allow Multiple Selections

The default behavior is that only one item can be selected at the same time. To support multiple choices, set the multiSelectionEnabled to true. Below is another example that allows multiple selections.

  enum Accessory { monitor, keyboard, mouse }

  class AccessoryOptions extends StatefulWidget {
    const AccessoryOptions({super.key});

    @override
    State<AccessoryOptions> createState() => _AccessoryOptionsState();
  }

  class _AccessoryOptionsState extends State<AccessoryOptions> {
    Set<Accessory> selectedAccessories = <Accessory>{Accessory.keyboard};

    @override
    Widget build(BuildContext context) {
      return SegmentedButton<Accessory>(
        selected: selectedAccessories,
        multiSelectionEnabled: true,
        onSelectionChanged: (Set<Accessory> newSelection) {
          setState(() {
            selectedAccessories = newSelection;
          });
        },
        segments: const <ButtonSegment<Accessory>>[
          ButtonSegment<Accessory>(
              value: Accessory.monitor,
              label: Text('Monitor'),
              icon: Icon(Icons.monitor),
          ),
          ButtonSegment<Accessory>(
              value: Accessory.keyboard,
              label: Text('Keyboard'),
              icon: Icon(Icons.keyboard),
          ),
          ButtonSegment<Accessory>(
              value: Accessory.mouse,
              label: Text('Mouse'),
              icon: Icon(Icons.mouse),
          ),
        ],
      );
    }
  }

Output:

Flutter - SegmentedButton - Allow Multiple Selections

Allow Empty Selection

By default, empty selection is disabled. That means if currently there is only one item selected and you click on that selected item, it will not be unselected. If you want to allow empty selection, pass the emptySelectionAllowed argument and set the value to true.

  SegmentedButton<Accessory>(
    // other arguments
    emptySelectionAllowed: true,
  )

Output:

Flutter - SegmentedButton - Allow Empty Selection

Configure Selected Icon

When an item is selected, the default appearance is a selected icon will be shown. If the button has an icon, it will be replaced by the selected icon. If you want to keep the button's icon or not showing the selected icon, it can be done by setting the showSelectedIcon to false.

  SegmentedButton<Accessory>(
    // other arguments
    showSelectedIcon: false,
  )

Output:

Flutter - SegmentedButton - Hide Selected Icon

The default icon is a check mark (Icons.check). It can be changed with another icon passed as the selectedIcon argument. You can also create a custom icon.

  SegmentedButton<Accessory>(
    // other arguments
    selectedIcon: const Icon(Icons.check_circle),
  )

Output:

Flutter - SegmentedButton - Custom Selected Icon

Custom Style

A ButtonStyle value can be passed to the style argument to customize the appearance of the buttons. The example below sets custom colors for the background and foreground.

  SegmentedButton<Accessory>(
    // other arguments
    style: ButtonStyle(
      backgroundColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
        return states.contains(MaterialState.selected)
            ? Colors.teal
            : Colors.white;
      }),
      foregroundColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
        return states.contains(MaterialState.selected)
            ? Colors.white
            : Colors.teal;
      }),
    ),
  )

Output:

Flutter - SegmentedButton - Custom Style

SegmentedButton Parameters

Below is the list of parameters you can pass to the constructor of SegmentedButton.

  • Key? key: The widget's key, used to control how a widget is replaced with another.
  • required List<ButtonSegment<T>> segments: List of segments.
  • required Set<T> selected: Set of selected segments.
  • Function(Set<T>)? onSelectionChanged: Function to be called when the selection changes.
  • bool multiSelectionEnabled: Whether to allow multiple segment selection. Defaults to false.
  • bool emptySelectionAllowed: Whether it's allowed to have no segment selected. Defaults to false.
  • ButtonStyle? style: Style for customizing the button.
  • bool showSelectedIcon: Whether the selected icon should be displayed on the selected segments. Defaults to false.
  • Widget? selectedIcon: Icon used to indicate whether a segment is selected. Defaults to Icons.check.

ButtonSegment Parameters

Below is the list of parameters you can pass to the constructor of ButtonSegment.

  • required T value: Value for identifying the segment.
  • Widget? icon: Icon to be displayed in the segment (optional).
  • Widget? label: Label to be displayed in the segment (optional).
  • bool enabled: Whether the segment is available for selection. Defaults to true.

Full Code

  import 'package:flutter/material.dart';

  void main() => runApp(const MyApp());

  class MyApp extends StatelessWidget {

    const MyApp({Key? key}) : super(key: key);

    @override
    Widget build(BuildContext context) {
      return const MaterialApp(
        title: 'Woolha.com Flutter Tutorial',
        home: SegmentedButtonExample(),
      );
    }
  }

  class SegmentedButtonExample extends StatelessWidget{
    const SegmentedButtonExample({super.key});

    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: const Text('Woolha.com Flutter Tutorial'),
          backgroundColor: Colors.teal,
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: const <Widget>[
              Spacer(),
              Text('Color options'),
              ColorOptions(),
              SizedBox(height: 20),
              Text('Accessory options'),
              AccessoryOptions(),
              Spacer(),
            ],
          ),
        ),
      );
    }
  }

  ButtonStyle buttonStyle = ButtonStyle(
    backgroundColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
      return states.contains(MaterialState.selected)
          ? Colors.teal
          : Colors.white;
    }),
    foregroundColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
      return states.contains(MaterialState.selected)
          ? Colors.white
          : Colors.teal;
    }),
  );

  enum Color { red, green, blue, yellow }

  class ColorOptions extends StatefulWidget {
    const ColorOptions({super.key});

    @override
    State<ColorOptions> createState() => _ColorOptionsState();
  }

  class _ColorOptionsState extends State<ColorOptions> {
    Color _selectedColor = Color.blue;

    @override
    Widget build(BuildContext context) {
      return SegmentedButton<Color>(
        selected: <Color>{_selectedColor},
        onSelectionChanged: (Set<Color> newSelection) {
          setState(() {
            _selectedColor = newSelection.first;
          });
        },
        segments: const <ButtonSegment<Color>>[
          ButtonSegment<Color>(
              value: Color.red,
              label: Text('Red'),
          ),
          ButtonSegment<Color>(
              value: Color.green,
              label: Text('Green'),
          ),
          ButtonSegment<Color>(
              value: Color.blue,
              label: Text('Blue'),
          ),
          ButtonSegment<Color>(
              value: Color.yellow,
              label: Text('Yellow'),
          ),
        ],
        // style: buttonStyle,
      );
    }
  }

  enum Accessory { monitor, keyboard, mouse }

  class AccessoryOptions extends StatefulWidget {
    const AccessoryOptions({super.key});

    @override
    State<AccessoryOptions> createState() => _AccessoryOptionsState();
  }

  class _AccessoryOptionsState extends State<AccessoryOptions> {
    Set<Accessory> selectedAccessories = <Accessory>{Accessory.keyboard};

    @override
    Widget build(BuildContext context) {
      return SegmentedButton<Accessory>(
        selected: selectedAccessories,
        onSelectionChanged: (Set<Accessory> newSelection) {
          setState(() {
            selectedAccessories = newSelection;
          });
        },
        multiSelectionEnabled: true,
        emptySelectionAllowed: true,
        showSelectedIcon: false,
        selectedIcon: const Icon(Icons.check_circle),
        style: buttonStyle,
        style: ButtonStyle(
          backgroundColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
            return states.contains(MaterialState.selected)
                ? Colors.teal
                : Colors.white;
          }),
          foregroundColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
            return states.contains(MaterialState.selected)
                ? Colors.white
                : Colors.teal;
          }),
        ),
        segments: const <ButtonSegment<Accessory>>[
          ButtonSegment<Accessory>(
              value: Accessory.monitor,
              label: Text('Monitor'),
              icon: Icon(Icons.monitor),
          ),
          ButtonSegment<Accessory>(
              value: Accessory.keyboard,
              label: Text('Keyboard'),
              icon: Icon(Icons.keyboard),
          ),
          ButtonSegment<Accessory>(
              value: Accessory.mouse,
              label: Text('Mouse'),
              icon: Icon(Icons.mouse),
          ),
        ],
      );
    }
  }

Summary

Flutter's SegmentedButton widget makes it easy to implement Material Design's segmented button. The usage is quite simple, just call the constructor by defining the segments and the currently active segments. You also need to pass a function that handles selection changes. It supports both single choice and multiple choices depending on the configuration. You can also enable empty selection, change the selected icon, and customize the style of the buttons.