How to Implement a Dropdown in Flutter: Material Widgets and Form Integration
To implement a dropdown in Flutter, use the DropdownButton<T> widget from the Material library for basic selection, or DropdownButtonFormField<T> when you require form validation and error handling.
Flutter's Material design library provides a composable architecture for dropdown menus centered around generic type handling and overlay management. The implementation in packages/flutter/lib/src/material/dropdown.dart uses a DropdownRoute to position menus relative to the button's render box, enabling any data type to serve as a dropdown value through DropdownMenuItem<T> widgets. Understanding the core widgets and their responsibilities allows you to customize selection behavior, theming, and validation logic.
Core Widgets for Dropdown Implementation in Flutter
DropdownButton
The DropdownButton<T> class, defined in packages/flutter/lib/src/material/dropdown.dart, serves as the primary interface for dropdown interaction. It displays the currently selected value and manages the onChanged callback that fires when users select an item. When tapped, this widget creates a DropdownRoute that inserts the menu into Navigator.of(context).overlay, calculating position using RelativeRect from the button's render box.
DropdownMenu
Located in packages/flutter/lib/src/material/dropdown_menu.dart, the DropdownMenu<T> widget handles the overlay presentation of selectable items. This widget builds a scrollable ListView containing the supplied DropdownMenuItem widgets and manages keyboard navigation (arrow keys) and animated transitions. The route's buildPage method returns this widget positioned according to the button's screen coordinates.
DropdownButtonFormField
For form integration, packages/flutter/lib/src/material/dropdown_form_field.dart provides DropdownButtonFormField<T>, which wraps DropdownButton in a FormField<T>. This enables standard form behaviors including validator, onSaved, and autovalidateMode hooks. Errors render below the button using the decoration property, consistent with other form field implementations.
DropdownMenuThemeData
Visual styling flows through DropdownMenuThemeData in packages/flutter/lib/src/material/dropdown_menu_theme.dart. This theme object controls the menu's backgroundColor, elevation, shadowColor, and shape, accessed via Theme.of(context).dropdownMenuTheme or DropdownMenuTheme.of(context).
Architectural Flow of Flutter Dropdowns
The dropdown implementation follows a strict lifecycle:
-
Trigger: User taps the
DropdownButton, which calls internal methods to create aDropdownRoute. -
Positioning: The route calculates a
RelativeRectusing the button's render box local-to-global coordinates relative to the overlay. -
Rendering: The route's
buildPagereturns aDropdownMenucontaining theitemslist wrapped in aMaterialwidget with theme-aware styling. -
Selection: Tapping a
DropdownMenuIteminvokes the button'sonChangedcallback with the selected value, then pops the route. -
Validation: If using
DropdownButtonFormField, the form'svalidate()method triggers the field's validator against the current value.
Practical Implementations
Basic DropdownButton
This example demonstrates a simple string-based dropdown using DropdownButton<String>:
String? _selectedFruit;
DropdownButton<String>(
value: _selectedFruit,
hint: const Text('Select a fruit'),
items: <String>['Apple', 'Banana', 'Cherry']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? newValue) {
setState(() {
_selectedFruit = newValue;
});
},
);
Form Validation with DropdownButtonFormField
For forms requiring validation, wrap the dropdown in DropdownButtonFormField with a GlobalKey<FormState>:
final _formKey = GlobalKey<FormState>();
String? _selectedCountry;
Form(
key: _formKey,
child: Column(
children: [
DropdownButtonFormField<String>(
decoration: const InputDecoration(
labelText: 'Country',
border: OutlineInputBorder(),
),
hint: const Text('Choose a country'),
items: ['USA', 'Canada', 'Mexico']
.map((c) => DropdownMenuItem(value: c, child: Text(c)))
.toList(),
validator: (value) =>
value == null ? 'Please select a country' : null,
onChanged: (value) => _selectedCountry = value,
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Process valid selection
}
},
child: const Text('Submit'),
),
],
),
);
Custom Theming
Apply app-wide or local styling using DropdownMenuThemeData:
Theme(
data: Theme.of(context).copyWith(
dropdownMenuTheme: DropdownMenuThemeData(
backgroundColor: Colors.grey[850],
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
child: DropdownButton<String>(
value: _selectedTheme,
items: ['Light', 'Dark', 'System']
.map((t) => DropdownMenuItem(value: t, child: Text(t)))
.toList(),
onChanged: (v) => setState(() => _selectedTheme = v),
),
);
Advanced: Direct DropdownMenu Usage
For custom button implementations, manually push a DropdownRoute from packages/flutter/lib/src/material/dropdown.dart:
ElevatedButton(
onPressed: () {
final button = context.findRenderObject() as RenderBox;
final overlay = Navigator.of(context).overlay!.context.findRenderObject() as RenderBox;
final position = RelativeRect.fromRect(
Rect.fromPoints(
button.localToGlobal(Offset.zero, ancestor: overlay),
button.localToGlobal(button.size.bottomRight(Offset.zero), ancestor: overlay),
),
Offset.zero & overlay.size,
);
Navigator.of(context).push(
DropdownRoute<String>(
items: ['One', 'Two', 'Three']
.map((s) => DropdownMenuItem(value: s, child: Text(s)))
.toList(),
position: position,
onSelected: (value) => setState(() => _selected = value),
),
);
},
child: const Text('Open Menu'),
);
Summary
DropdownButton<T>inpackages/flutter/lib/src/material/dropdown.dartprovides the standard button interface and selection callbacks.DropdownMenu<T>inpackages/flutter/lib/src/material/dropdown_menu.dartrenders the overlay menu with keyboard navigation and animations.DropdownButtonFormField<T>inpackages/flutter/lib/src/material/dropdown_form_field.dartadds form validation, error display, and integration withFormwidgets.DropdownMenuThemeDatacontrols visual styling including background color, elevation, and shape through the theme system.- The generic type parameter
Tallows any data type to serve as dropdown values, not just strings.
Frequently Asked Questions
How do I validate a dropdown selection in Flutter?
Use DropdownButtonFormField<T> from packages/flutter/lib/src/material/dropdown_form_field.dart instead of the standard DropdownButton. This widget wraps the dropdown in a FormField, exposing the validator property that accepts a function returning a string error message or null. When Form.of(context).validate() runs, it checks the current dropdown value against your validation logic and displays errors using the provided InputDecoration.
What's the difference between DropdownButton and DropdownMenu?
DropdownButton is a high-level widget that combines the button surface and menu management, handling tap detection and route creation automatically. DropdownMenu, located in packages/flutter/lib/src/material/dropdown_menu.dart, is the lower-level widget that actually renders the scrollable overlay list. While most implementations use DropdownButton, you can manually instantiate DropdownMenu through DropdownRoute when you need complete control over the trigger widget or positioning logic.
How do I customize the background color and shape of the dropdown menu?
Wrap your DropdownButton in a Theme widget and override dropdownMenuTheme with DropdownMenuThemeData. This class, defined in packages/flutter/lib/src/material/dropdown_menu_theme.dart, exposes backgroundColor, elevation, shadowColor, and shape properties. Alternatively, set these values in your app-wide ThemeData to apply the styling consistently across all dropdowns in your application.
Can I use custom objects instead of primitive types for dropdown values?
Yes. The dropdown widgets use Dart generics, allowing you to specify any type for the value parameter. Define your data class, then create DropdownMenuItem<YourClass> widgets where the value is your object instance and the child displays the appropriate label. Ensure your objects properly implement equality checks, as the dropdown compares values using == to determine the currently selected item.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →