Styling a Flutter DropdownButton Widget with Color and Some ✨Bling

Sylvia Dieckmann
5 min readJun 3, 2021

Most Flutter apps require some data input and often a drop down menu is the widget of choice when selecting a value from a limited set of options.

Setting up a DropdownButton Widget is well documented and doesn’t need another blog post describing just that. However, I recently came across a situation where the options had to be styled according to their values which turned into a fun experiment.

Image 1: This is the goal. Each option styled with color and label. When making a choice, the button and some other field get restyled.

To get to the layout in the first image, I start wiring up a plain drop down button. As always I begin with a stripped-down, plain vanilla flutter template and replace the counter demo with my own.

[To follow along feel free to check out the drop_down_button_styling branch in this gitlab repo.]

Now I need some data to populate my menu with. To keep things simple, I quickly define a basic ColorItem class, add a static list of said ColorItems to my state, and wire up a plain DropdownButton widget.

class ColorItem {
ColorItem(this.name, this.color);
final String name;
final Color color;
}
[...]class _MyHomePageState extends State<MyHomePage> {
final List<ColorItem> items = [
ColorItem("geen", Colors.green),
ColorItem("yellow", Colors.yellow),
ColorItem("blue", Colors.blue),
];
late ColorItem currentChoice; @override
void initState() {
currentChoice = items[0];
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Icon(
Icons.face,
color: currentChoice.color,
size: 100.0,
),
DropdownButton(
isExpanded: true,
style: Theme.of(context).textTheme.headline6,
value: currentChoice,
items: items
.map<DropdownMenuItem<ColorItem>>(
(ColorItem item) => DropdownMenuItem<ColorItem>(
value: item,
child: Center(child: Text(item.name)),
))
.toList(),
onChanged: (ColorItem? value) =>
setState(() => currentChoice = value!),
),
],
),
),
);
}
}

Image 2 shows how this looks.

[Oh, why the good folks at Google choose to not highlight the currently selected item in the opened menu on the right site is one of the remaining mysteries in this world. They do it better with the PopupMenuButton. (Check here for a side-by-side.)

Luckily, the steps described in this tutorial can also be used to style the currently selected item of a DropdownMenuItem in a more user-friendly way.]

Image 2

At this point the control is wired up well enough and it is time to start with some styling. First we add the background color.

items: items
.map<DropdownMenuItem<ColorItem>>(
(ColorItem item) => DropdownMenuItem<ColorItem>(
value: item,
child: Container(
alignment: Alignment.center,
constraints: BoxConstraints(minHeight: 48.0),
color: item.color,
child: Text(item.name),
),
))
.toList(),

Note how styling the DropdownMenuItem list affects the styles of both opened and collapsed menus in image 3, although the two states might not turn out completely identical.

Image 3: Note the little gap for the caret on the right hand side of the left image. We should fix that.

One thing I don’t like about this solution is that the color blocks contract slightly to make room for the icon on the right when collapsed.

As a quick fix, I just wrap the entire button in a box and fix color and margin.

Container(
margin: EdgeInsets.all(16),
constraints: BoxConstraints(minHeight: 48.0),
color: currentChoice.color,
child: DropdownButton([...]),
),

This would address the strange placement of the drop down indicator icon but I want to try out a completely different approach. Rather than tinkering with sizes to force the styled menu items to look the same regardless of their state, I want a completely different style for the collapsed button.

DropdownButton has an attribute selectedItemBuilder which can be used for this purpose. Note how this attribute has a similar signature than the items attribute, but with an additional function wrapper:

child: DropdownButton(
isExpanded: true,
style: Theme.of(context).textTheme.headline6,
value: currentChoice,
selectedItemBuilder: (BuildContext context) => items
.map<Widget>((ColorItem item) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: 16.0),
child: Icon(Icons.palette, color: item.color)),
Text(item.name),
],
))
.toList(),

items: items
.map<DropdownMenuItem<ColorItem>>(
(ColorItem item) => DropdownMenuItem<ColorItem>(
value: item,
child: Container(
alignment: Alignment.center,
constraints: BoxConstraints(minHeight: 48.0),
color: item.color,
child: Text(item.name),
),
))
.toList(),
onChanged: (ColorItem? value) =>
setState(() => currentChoice = value!),
),
Image 4: DropDownButton widget in collapsed state left, opened state right.

[In my practice run I ended up referring to the design in image 4 as the “bling solution.” It would probably not impress my UX specialist friends, and I quietly dropped it from my production code. Oh well. But at least it helped to demo the power of selectedItemBuilder…]

Almost done but before I wrap up I would like to add one little gimmick. In my real-life app, the color spectrum is much bigger and some of the labels are hard to read on the colored background. Unlike in the demo, the colors are also dynamically picked by the user, so specifying a labelColor for each ColorItem is not feasible.

Luckily there is a simple solution. Inside my DropdownMenuItem constructor call I just need to add a few lines to style the text according to the color brightness of the current color. Awesome.

items: items
.map<DropdownMenuItem<ColorItem>>(
(ColorItem item) => DropdownMenuItem<ColorItem>(
value: item,
child: Container(
alignment: Alignment.center,
constraints: BoxConstraints(minHeight: 48.0),
color: item.color,
child: Text(
item.name,
style: TextStyle(
color: ThemeData.estimateBrightnessForColor(
item.color) ==
Brightness.dark
? Colors.white
: Colors.black,
),

),
),
))
.toList(),

And with this, I am done. Should my latest side project ever see the light of the play store you will probably recognize one of the input widgets :-)

Closing note: While I am used to presenting my tech work in talks, I am a novice tech writer. Please bear with me while I figure out the best presentation style within the limitations of this hosting platform.

Of course, any feedback on style, presentation, and content is greatly appreciated! Thanks for reading!

--

--