Flutter - Using Radio Widget Examples

This tutorial shows you how to create a radio group using the Radio widget in Flutter.

If you have a list of options where a user can only select one of them at the same time, it's quite common to create a radio group consisting of several radio buttons. A radio button usually uses an empty outer circle icon, with a solid inner circle if it's currently being selected. In Flutter, you can use the Radio widget to create a radio button. The usage is quite simple and you can also customize the button.

Using Radio

Below is the constructor to use.

  const Radio({
    Key? key,
    required dynamic value,
    required dynamic groupValue,
    required ValueChanged<T?>? onChanged,
    MouseCursor? mouseCursor,
    bool toggleable = false,
    Color? activeColor,
    MaterialStateProperty<Color?>? fillColor,
    Color? focusColor,
    Color? hoverColor,
    MaterialStateProperty<Color?>? overlayColor,
    double? splashRadius,
    MaterialTapTargetSize? materialTapTargetSize,
    VisualDensity? visualDensity,
    FocusNode? focusNode,
    bool autofocus = false,
  })

As you can see from the constructor, the required arguments are value, groupValue, and onChanged. The other arguments are optional and can be used to change the style and behavior of the radio button.

Set Value and Group Value

A radio button must have a value passed as the value argument. To specify what value is currently being selected in a group, you have to pass the groupValue argument. A radio button is in selected state if its value matches the groupValue. Since a group can only have one selected value, the selected group value must be the same across all radio buttons in a group. Therefore, it's recommended to pass the same variable (e.g. using a state variable) as the groupValue argument, so that you only need to update a variable for changing the currently selected value. If you want to set one of the buttons to be selected initially, just set the button's value as the initial value of the variable passed as the groupValue. In almost all cases, the value must be unique among the buttons in the same group, unless you allow more than one radio button selected at the same time — which is unusual.

The currently selected value can be changed when the user clicks on a radio button. You have to handle that event inside a callback function passed as onChanged argument. If you use a state variable to store the selected value, you need to update it.

In this tutorial, we are going to create a radio group whose value type is integer. First, create a state variable for the group value.

  int? _groupValue;

The Radio widget only creates and handles the radio button. If you want to add a text, you need to add it separately. For example, you can use the ListTile widget and pass a Text widget as the title argument and a Radio as the leading argument. Below is a method for creating a radio option, including the button and the text. For using the Radio widget, make sure you pass all the required arguments.

  Widget _buildItem(String text, int value) {
    return ListTile(
      title: Text(text),
      leading: Radio<int>(
        groupValue: _groupValue,
        value: value,
        onChanged: (int? value) {
          setState(() {
            _groupValue = value;
          });
        },
      ),
    );
  }

Output:

Flutter - Radio

Set Colors

Flutter allows you to change the button color. When the button is in the selected state, you can set the color by passing the activeColor argument. When the button has the current focus, you can set a different color by passing the focusColor argument. You can also set the color when a pointer is hovering over the button (on the web browser) using the hoverColor argument. The color set as the activeColor affects both the empty (outer) and the solid (inner) circles. Meanwhile, the colors set as the focusColor and hoverColor affect the entire button other than the empty and the solid circles. In other words, it affects the background color of the button, which is usually referred to as Material ink splash. The splash is only shown in certain states such as focused and hovered.

  Widget _buildItem(String text, int value) {
    return ListTile(
      title: Text(text),
      leading: Radio<int>(
        groupValue: _groupValue,
        value: value,
        onChanged: (int? value) {
          setState(() {
            _groupValue = value;
          });
        },
      ),
      hoverColor: Colors.yellow,
      activeColor: Colors.pink,
      focusColor: Colors.purple,
      // other arguments
    );
  }

Output:

Flutter - Radio - Colors

Another way to set the colors is using the fillColor argument whose type is MaterialStateProperty. You can determine the color to use based on the current state. fillColor only sets the color for the outer and inner circles. Be careful that using fillColor overrides the color passed as the activeColor argument.

  Widget _buildItem(String text, int value) {
    return ListTile(
      title: Text(text),
      leading: Radio<int>(
        groupValue: _groupValue,
        value: value,
        onChanged: (int? value) {
          setState(() {
            _groupValue = value;
          });
        },
      ),
      hoverColor: Colors.yellow,
      activeColor: Colors.pink,
      focusColor: Colors.purple,
      fillColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
        if (states.contains(MaterialState.hovered)) {
          return Colors.orange;
        } else if (states.contains(MaterialState.selected)) {
          return Colors.teal;
        } if (states.contains(MaterialState.focused)) {
          return Colors.blue;
        } else {
          return Colors.black12;
        }
      }),
      // other arguments
    );
  }

Output:

Flutter - Radio - Fill Colors

Another argument for customizing the colors is overlayColor. The usage is similar to fillColor, which allows you to determine the color based on the current state. The difference is overlayColor is used for setting the Material ink splash rather than the color of the circles. Be careful that it will override the colors set as focusColor and hoverColor arguments if you pass any of them.

  Widget _buildItem(String text, int value) {
    return ListTile(
      title: Text(text),
      leading: Radio<int>(
        groupValue: _groupValue,
        value: value,
        onChanged: (int? value) {
          setState(() {
            _groupValue = value;
          });
        },
      ),
      hoverColor: Colors.yellow,
      activeColor: Colors.pink,
      focusColor: Colors.purple,
      overlayColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
        if (states.contains(MaterialState.hovered)) {
          return Colors.lightGreenAccent;
        } if (states.contains(MaterialState.focused)) {
          return Colors.brown;
        } else {
          return Colors.white;
        }
      }),
      // other arguments
    );
  }

Output:

Flutter - Radio - Overlay Colors

Set Splash Radius

The Material ink splash of a button is shown in certain states which include hovered or focused. You can change the radius of the splash by passing a double value as the splashRadius argument.

  Widget _buildItem(String text, int value) {
    return ListTile(
      title: Text(text),
      leading: Radio<int>(
        groupValue: _groupValue,
        value: value,
        onChanged: (int? value) {
          setState(() {
            _groupValue = value;
          });
        },
      ),
      overlayColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
        if (states.contains(MaterialState.hovered)) {
          return Colors.lightGreenAccent;
        } if (states.contains(MaterialState.focused)) {
          return Colors.brown;
        } else {
          return Colors.white;
        }
      }),
      splashRadius: 25,
      // other arguments
    );
  }

Output:

Flutter - Radio - Splash Radius

Allow/Disallow Toggle

In order to unselect a radio button, usually the user needs to select another one. If you want to allow a radio button to be unselected without requiring the user to select another one, you can pass the toggleable argument and set the value to true. The value defaults to false if you don't pass it.

  Widget _buildItem(String text, int value) {
    return ListTile(
      title: Text(text),
      leading: Radio<int>(
        groupValue: _groupValue,
        value: value,
        onChanged: (int? value) {
          setState(() {
            _groupValue = value;
          });
        },
      ),
      toggleable: true,
      // other arguments
    );
  }

Output:

Flutter - Radio - Toggleable

Set Visual Density

You can change the density of the layout by using the visualDensity argument. The value you need to pass is a VisualDensity. You can use one of the static constants or create your own custom VisualDensity

  Widget _buildItem(String text, int value) {
    return ListTile(
      title: Text(text),
      leading: Radio<int>(
        groupValue: _groupValue,
        value: value,
        onChanged: (int? value) {
          setState(() {
            _groupValue = value;
          });
        },
      ),
      visualDensity: VisualDensity.compact,
      // other arguments
    );
  }

Output:

Flutter - Radio - Visual Density

Set Minimum Tap Target Size

Ideally, radio& buttons should have a minimum tap target size so that the users can comfortably select a particular option. You can change it by passing a MaterialTapTargetSize enum as the materialTapTargetSize argument.

  Widget _buildItem(String text, int value) {
    return ListTile(
      title: Text(text),
      leading: Radio<int>(
        groupValue: _groupValue,
        value: value,
        onChanged: (int? value) {
          setState(() {
            _groupValue = value;
          });
        },
      ),
      materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
      // other arguments
    );
  }

Set Focus Node

The constructor has an argument named focusNode which allows you to pass a FocusNode to use for the widget. In the example below, we are going to set a custom FocusNode for each button, with the first button has the current focus. First, create the FocusNodes and set the focus to the first one.

  late List<FocusNode> _focusNodes;

  @override
  void initState() {
    _focusNodes = Iterable<int>.generate(3)
        .map((e) => FocusNode())
        .toList();

    _focusNodes[0].requestFocus();
  }

Then, modify the _buildItem method above to accept a FocusNode which will be passed as the focusNode argument.

  Widget _buildItem(String text, int value, FocusNode focusNode) {
    return ListTile(
      title: Text(text),
      leading: Radio<int>(
        groupValue: _groupValue,
        value: value,
        onChanged: (int? value) {
          setState(() {
            _groupValue = value;
          });
        },
        focusNode: focusNode,
        // other arguments
      ),
    );
  }

Radio - Parameters

  • Key? key: The widget's key, used to control how a widget is replaced with another widget.
  • required dynamic value: The value for the radio button.
  • required dynamic groupValue: The current value for a group of radio buttons.
  • required ValueChanged<T?>? onChanged: A callback function that will be when the user selects the radio button.
  • MouseCursor? mouseCursor: The cursor for a mouse pointer when it enters or is hovering over the widget on the web. If null, RadioThemeData.mouseCursor is used. If RadioThemeData.mouseCursor is also null, MaterialStateMouseCursor.clickable is used.
  • bool toggleable: yyy. Defaults to false.
  • Color? activeColor: The color to use when this radio button is selected.
  • MaterialStateProperty<Color?>? fillColor: The color that fills the radio button which can be differentiated for each MaterialStates.
  • Color? focusColor: The color for the radio's Material when it has the input focus.
  • Color? hoverColor: The color for the radio's Material when a pointer is hovering over it.
  • MaterialStateProperty<Color?>? overlayColor: The color of the button's Material.
  • double? splashRadius: The splash radius of the circular Material ink response.
  • MaterialTapTargetSize? MaterialTapTargetSize: Configures the minimum size of the tap target.
  • VisualDensity? visualDensity: The compactness of the layout.
  • FocusNode? focusNode: The focus node to use for the widget.
  • bool autofocus: Whether it should be selected as the initial focus when no other node in its scope is currently focused. Defaults to false.

?: value can be null.
required: value must be passed.

Full Code

  import 'package:flutter/material.dart';
  
  void main() => runApp(RadioExample());
  
  class MyApp extends StatelessWidget {
  
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Woolha.com Flutter Tutorial',
        home: RadioExample(),
      );
    }
  }
  
  class RadioExample extends StatefulWidget {
  
    @override
    State createState() => new _RadioExampleState();
  }
  
  class _RadioExampleState extends State<RadioExample> {
  
    int? _groupValue;
  
    late List<FocusNode> _focusNodes;
  
    @override
    void initState() {
      _focusNodes = Iterable<int>.generate(3)
          .map((e) => FocusNode())
          .toList();
  
      _focusNodes[0].requestFocus();
    }
  
    Widget _buildItem(String text, int value, FocusNode focusNode) {
      return ListTile(
        title: Text(text),
        leading: Radio<int>(
          groupValue: _groupValue,
          value: value,
          onChanged: (int? value) {
            setState(() {
              _groupValue = value;
            });
          },
          hoverColor: Colors.yellow,
          activeColor: Colors.pink,
          focusColor: Colors.purple,
          fillColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
            if (states.contains(MaterialState.hovered)) {
              return Colors.orange;
            } else if (states.contains(MaterialState.selected)) {
              return Colors.teal;
            } if (states.contains(MaterialState.focused)) {
              return Colors.blue;
            } else {
              return Colors.black12;
            }
          }),
          overlayColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
            if (states.contains(MaterialState.hovered)) {
              return Colors.lightGreenAccent;
            } if (states.contains(MaterialState.focused)) {
              return Colors.brown;
            } else {
              return Colors.white;
            }
          }),
          splashRadius: 25,
          toggleable: true,
          visualDensity: VisualDensity.compact,
          materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
          focusNode: focusNode,
        ),
      );
    }
  
    @override
    Widget build(BuildContext context) {
      return new Scaffold(
        appBar: AppBar(
          title: const Text('Woolha.com Flutter Tutorial'),
        ),
        body: Column(
          children: [
            _buildItem("One", 1, _focusNodes[0]),
            _buildItem("Two", 2, _focusNodes[1]),
            _buildItem("Three", 3, _focusNodes[2]),
          ],
        ),
      );
    }
  }

Summary

That's how to use the Radio widget in Flutter. There are three required arguments that must be passed — value, groupValue, and onChanged. You can also pass the other arguments to change the style and behavior. If you want to create something with similar functionality to a radio group, but without using the conventional radio button icon, it's recommended to read our tutorial about creating custom radio button in Flutter.