Select
A dropdown select component for choosing options from a list
A dropdown select component for choosing from a list of options.
When to use this
- Form inputs: Collect user selections from predefined options
- Filters: Allow users to filter content by categories or criteria
- Settings: Enable users to choose configuration values
- Data entry: Provide structured input options in forms
Anatomy
The select trigger is a fixed horizontal row with four visual slots:
container— layout, fill, border, effects, and spacing for the trigger.icon— an optional content icon before the label or placeholder.label/placeholder— the selected item label or empty-state text.indicator— the affordance that communicates collapsed or expanded state. It is styled separately fromicon.
Customize the indicator glyph for either state through RemixSelectTrigger:
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
void main() {
RemixSelectTrigger(
placeholder: 'Select an option',
icon: Icons.language,
collapsedIcon: Icons.add,
expandedIcon: Icons.remove,
);
}Each state falls back independently. A null collapsedIcon uses the built-in
downward chevron, while a null expandedIcon uses the built-in upward chevron.
Those defaults are internal vector paths and do not require Material icons.
Basic implementation
Basic implementation
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
class SelectExample extends StatefulWidget {
const SelectExample({super.key});
@override
State<SelectExample> createState() => _SelectExampleState();
}
class _SelectExampleState extends State<SelectExample> {
String? _selectedValue;
@override
Widget build(BuildContext context) {
return SizedBox(
width: 200,
child: RemixSelect(
trigger: const RemixSelectTrigger(placeholder: 'Text Value'),
items: [
RemixSelectItem(
value: 'option1',
label: 'Option 1',
enabled: true,
style: itemStyle,
),
RemixSelectItem(
value: 'option2',
label: 'Option 2',
enabled: false,
style: itemStyle,
),
],
selectedValue: _selectedValue,
style: style,
onChanged: (value) {
setState(() {
_selectedValue = value;
});
},
),
);
}
SelectMenuItemStyler get itemStyle {
return SelectMenuItemStyler()
.iconSize(16)
.padding(.all(8))
.borderRadius(.all(const Radius.circular(8)))
.onHovered(SelectMenuItemStyler().color(Colors.blueGrey.shade50))
.onDisabled(
SelectMenuItemStyler().labelColor(Colors.grey.shade300),
);
}
SelectStyler get style {
return SelectStyler()
.trigger(
SelectTriggerStyler()
.color(Colors.transparent)
.border(.color(const Color(0xFF898988)))
.padding(.vertical(10))
.padding(.horizontal(12))
.borderRadius(.all(const Radius.circular(12))),
)
.menuContainer(
FlexBoxStyler()
.width(200)
.margin(.vertical(5))
.padding(.all(6))
.color(Colors.white)
.borderRadius(.all(const Radius.circular(12))),
);
}
}Long lists
The option list scrolls when it is taller than the panel. The panel fits the
space beside the trigger: it is laid out no taller than the room above or below
the trigger, after the positioning sideOffset and collisionPadding, and
opens on the side with more room, so a long list scrolls there instead of
covering the trigger. A maxHeight on the content style, or on
menuContainer, caps the panel lower still. Padding and decoration on either
container stay in place while the options scroll. If neither side has
positioning.minimumAvailableHeight of room (48 logical pixels by default),
the panel may overlap the trigger to keep options usable, while respecting
collisionPadding. Increase this threshold for larger rows or panel padding;
it is a placement policy, not a guarantee for arbitrary content or a minimum
panel size. Set it to zero to retain strict side-capping, even at zero headroom.
A short
panel that fits still stays beside its trigger. With avoidCollisions: false,
only the requested side's space is used and the fallback does not apply.
import 'package:remix/remix.dart';
void main() {
SelectStyler().content(SelectContentStyler().maxHeight(320));
}Match the trigger width
A select menu is as wide as its widest option. remixAnchorWidth is the width
of the trigger that opened the menu, so a width on the menu can follow it, like
CSS min-width: var(--trigger-width):
Menu at least as wide as the trigger
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
class TriggerWidthSelectExample extends StatefulWidget {
const TriggerWidthSelectExample({super.key});
@override
State<TriggerWidthSelectExample> createState() =>
_TriggerWidthSelectExampleState();
}
class _TriggerWidthSelectExampleState extends State<TriggerWidthSelectExample> {
String? _selectedValue;
@override
Widget build(BuildContext context) {
return SizedBox(
width: 280,
child: RemixSelect<String>(
trigger: const RemixSelectTrigger(placeholder: 'Pick a fruit'),
items: const [
RemixSelectItem(value: 'apple', label: 'Apple'),
RemixSelectItem(value: 'fig', label: 'Fig'),
],
selectedValue: _selectedValue,
positioning: const OverlayPositionConfig(
side: .bottom,
alignment: .start,
),
style: SelectStyler().content(
SelectContentStyler().minWidth(remixAnchorWidth()),
),
onChanged: (value) => setState(() => _selectedValue = value),
),
);
}
}- Pass it to
minWidthfor a menu at least as wide as the trigger,widthfor exactly as wide, ormaxWidthfor no wider. The menu follows the trigger if it resizes while open. - It is opt-in. Without it the menu keeps the width its styles give it.
- The token resolves where the style is built.
RemixSelectandRemixMenubuild their menu styles inside the overlay, so use it in the selectcontentandmenuContainerstyles or the menuoverlaystyle. A style resolved outside the overlay, such as the trigger's or aRemixPopover's, reads0. - Merging a
minWidthintocontentreplaces a minimum a preset set there. To keep it, apply the token as a box modifier:SelectContentStyler().wrap(.box(BoxStyler().minWidth(remixAnchorWidth())))makes the menu the larger of the preset's minimum and the trigger width. Do not put the token onmenuContainer: that box sits inside the padding and border ofcontent, so the menu ends up that much wider than the trigger. - On a single box, a
minWidthlarger than itsmaxWidthis invalid in Flutter and throws in debug. With the token that only happens once the trigger is wider than the maximum, so keep a ceiling on a different box than the token. - A menu wider than its trigger is centered below it by default. Use
alignment: .start, as above, to align the edges.
Fortal widgets
The application-owned Fortal preset provides a themed widget for this component:
Fortal variants
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
import 'ui/ui.dart';
class FortalSelectExample extends StatefulWidget {
const FortalSelectExample({super.key});
@override
State<FortalSelectExample> createState() => _FortalSelectExampleState();
}
class _FortalSelectExampleState extends State<FortalSelectExample> {
String? _surface;
String? _soft;
String? _ghost;
static const _items = [
RemixSelectItem(value: 'opt1', label: 'Option 1'),
RemixSelectItem(value: 'opt2', label: 'Option 2'),
];
@override
Widget build(BuildContext context) {
return Column(
spacing: 16,
children: [
FortalSelect.surface(
trigger: const RemixSelectTrigger(placeholder: 'Surface'),
items: _items,
selectedValue: _surface,
onChanged: (value) => setState(() => _surface = value),
),
FortalSelect.soft(
trigger: const RemixSelectTrigger(placeholder: 'Soft'),
items: _items,
selectedValue: _soft,
onChanged: (value) => setState(() => _soft = value),
),
FortalSelect.ghost(
trigger: const RemixSelectTrigger(placeholder: 'Ghost'),
items: _items,
selectedValue: _ghost,
onChanged: (value) => setState(() => _ghost = value),
),
],
);
}
}FortalSelect applies matching trigger, menu container, and default item styles. Set RemixSelectItem.style only for a row-level override.
See the fortalSelectStyle source code for all available options.
Reduced motion
When the user asks for reduced motion, the menu opens and closes without its fade and scale. See Reduced motion for the signals Remix reads.
Constructor
Constructor
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
RemixSelect<T> remixSelectConstructor<T>({
Key? key,
required RemixSelectTrigger trigger,
required List<RemixSelectItem<T>> items,
T? selectedValue,
OverlayPositionConfig positioning = const OverlayPositionConfig(
side: OverlaySide.bottom,
alignment: OverlayAlignment.center,
),
ValueChanged<T?>? onChanged,
VoidCallback? onOpen,
VoidCallback? onClose,
bool enabled = true,
MouseCursor mouseCursor = SystemMouseCursors.click,
String? semanticLabel,
bool closeOnSelect = true,
FocusNode? focusNode,
SelectStyler style = const SelectStyler.create(),
SelectSpec? styleSpec,
}) => throw UnimplementedError();Properties
Widget Properties
key → Key?
Optional. Controls how one widget replaces another widget in the tree.
trigger → RemixSelectTrigger
Required. The trigger data that defines the select's button.
RemixSelectTrigger requires a placeholder and optionally accepts a leading
content icon, collapsedIcon, and expandedIcon. Omitted state icons fall
back independently to the built-in indicator for that state.
items → List<RemixSelectItem<T>>
Required. The list of selectable items.
selectedValue → T?
Optional. The currently selected value.
onChanged → ValueChanged<T?>?
Optional. Called when the selected value changes. When null the select is disabled and cannot be opened — a no-op callback is not a read-only mode.
onOpen → VoidCallback?
Optional. Called when the dropdown opens.
onClose → VoidCallback?
Optional. Called when the dropdown closes.
enabled → bool
Optional. Whether the select is enabled and can be interacted with.
mouseCursor → MouseCursor
Optional. The mouse cursor for the select trigger. Defaults to
SystemMouseCursors.click; disabled selects use SystemMouseCursors.basic.
semanticLabel → String?
Optional. Semantic label for accessibility.
closeOnSelect → bool
Optional. Whether to automatically close the dropdown when an item is selected.
focusNode → FocusNode?
Optional. Optional focus node to control focus behavior.
style → SelectStyler
Optional. The style configuration for the select.
styleSpec → SelectSpec?
Optional. A raw resolved style spec that bypasses fluent style resolution.
Select Style Methods
content(SelectContentStyler value)
Configures the box around the menu: its fill, border, padding, and
constraints. Its width, minWidth, and maxWidth accept
remixAnchorWidth() to follow the trigger width.
menuContainer(FlexBoxStyler value)
Configures the select menu overlay container.
trigger(SelectTriggerStyler value)
Configures the select trigger style.
item(SelectMenuItemStyler value)
Configures the default style for every option. A RemixSelectItem.style
merges on top as an optional row-level override.
animate(AnimationConfig value)
Configures implicit animation for style transitions.
variants(List<VariantStyle<SelectSpec>> value)
Sets style variants.
foregroundDecoration(DecorationMix value)
Sets the menu container foreground decoration.
transform(Matrix4 value, AlignmentGeometry alignment = Alignment.center)
Applies a matrix transformation to the menu container.
wrap(WidgetModifierConfig value)
Applies widget modifiers such as clipping, opacity, or scaling.
modifier(WidgetModifierConfig value)
Sets the widget modifier.
call<T>({ ... })
Creates a RemixSelect widget with this style applied.
Select Trigger Style Methods
container(FlexBoxStyler value)
Configures the trigger container style.
label(TextStyler value)
Sets trigger label styling.
icon(IconStyler value)
Styles the optional trigger content icon.
indicator(IconStyler value)
Styles the collapsed/expanded indicator independently from the content icon.
indicatorOpacity(double value)
Sets the opacity of the collapsed/expanded indicator.
chevron and chevronOpacity were renamed to indicator and
indicatorOpacity. The indicator may now use any caller-provided glyph, so
the semantic name is the only supported spelling.
alignment(Alignment value)
Sets trigger container alignment.
constraints(BoxConstraintsMix value)
Sets trigger size constraints.
decoration(DecorationMix value)
Sets trigger container decoration.
margin(EdgeInsetsGeometryMix value)
Sets trigger container margin.
padding(EdgeInsetsGeometryMix value)
Sets trigger container padding.
foregroundDecoration(DecorationMix value)
Sets a foreground decoration painted on top of the trigger.
transform(Matrix4 value, AlignmentGeometry alignment = Alignment.center)
Applies a matrix transformation to the trigger.
animate(AnimationConfig value)
Configures implicit animation for trigger style transitions.
variants(List<VariantStyle<SelectTriggerSpec>> value)
Sets trigger style variants.
wrap(WidgetModifierConfig value)
Applies widget modifiers such as clipping, opacity, or scaling.
modifier(WidgetModifierConfig value)
Sets the trigger widget modifier.
labelStyle(TextStyleMix value)
Sets label/text style using TextStyleMix directly
labelColor(Color value)
Sets label/text color
labelFontSize(double value)
Sets label/text font size
labelFontWeight(FontWeight value)
Sets label/text font weight
labelFontStyle(FontStyle value)
Sets label/text font style (italic/normal)
labelLetterSpacing(double value)
Sets label/text letter spacing
labelDecoration(TextDecoration value)
Sets label/text decoration (underline, strikethrough, etc.)
labelFontFamily(String value)
Sets label/text font family
labelHeight(double value)
Sets label/text line height
labelWordSpacing(double value)
Sets label/text word spacing
labelDecorationColor(Color value)
Sets label/text decoration color
iconColor(Color value)
Sets icon color
iconSize(double value)
Sets icon size
iconOpacity(double value)
Sets icon opacity
iconWeight(double value)
Sets icon weight (useful for variable icons like Material Symbols)
iconGrade(double value)
Sets icon grade (useful for Material Icons)
iconFill(double value)
Sets icon fill (useful for Material Icons filled variants)
iconOpticalSize(double value)
Sets icon optical size (useful for Material Icons)
iconBlendMode(BlendMode value)
Sets icon blend mode
iconTextDirection(TextDirection value)
Sets icon text direction
iconShadows(List<ShadowMix> value)
Sets icon shadows
iconShadow(ShadowMix value)
Sets single icon shadow
Select Menu Item Style Methods
container(FlexBoxStyler value)
Configures the menu item container style.
containerEffects(RemixBoxEffectsMix value)
Configures an outline and paint layers drawn around the item row without
taking layout space, such as a focus ring in onFocusVisible. An outside ring
is cropped at the list edge once the options are long enough to scroll, and at
the popup edge when the popup content clips (clipBehavior on its container);
use a negative outlineOffset to draw it inside the row.
text(TextStyler value)
Sets menu item text styling.
label(TextStyler value)
Sets menu item text styling through the label alias.
icon(IconStyler value)
Sets menu item icon styling.
alignment(Alignment value)
Sets menu item container alignment.
constraints(BoxConstraintsMix value)
Sets menu item size constraints.
decoration(DecorationMix value)
Sets menu item container decoration.
margin(EdgeInsetsGeometryMix value)
Sets menu item container margin.
padding(EdgeInsetsGeometryMix value)
Sets menu item container padding.
foregroundDecoration(DecorationMix value)
Sets a foreground decoration painted on top of the menu item.
transform(Matrix4 value, AlignmentGeometry alignment = Alignment.center)
Applies a matrix transformation to the menu item.
animate(AnimationConfig value)
Configures implicit animation for menu item style transitions.
variants(List<VariantStyle<SelectMenuItemSpec>> value)
Sets menu item style variants.
wrap(WidgetModifierConfig value)
Applies widget modifiers such as clipping, opacity, or scaling.
modifier(WidgetModifierConfig value)
Sets the menu item widget modifier.
labelStyle(TextStyleMix value)
Sets item text style using TextStyleMix directly.
labelColor(Color value)
Sets item text color.
labelFontSize(double value)
Sets item text font size.
labelFontWeight(FontWeight value)
Sets item text font weight.
labelFontStyle(FontStyle value)
Sets item text font style.
labelLetterSpacing(double value)
Sets item text letter spacing.
labelDecoration(TextDecoration value)
Sets item text decoration.
labelFontFamily(String value)
Sets item text font family.
labelHeight(double value)
Sets item text line height.
labelWordSpacing(double value)
Sets item text word spacing.
labelDecorationColor(Color value)
Sets item text decoration color.
iconColor(Color value)
Sets item icon color.
iconSize(double value)
Sets item icon size.
iconOpacity(double value)
Sets item icon opacity.
iconWeight(double value)
Sets item icon weight.
iconGrade(double value)
Sets item icon grade.
iconFill(double value)
Sets item icon fill.
iconOpticalSize(double value)
Sets item icon optical size.
iconBlendMode(BlendMode value)
Sets item icon blend mode.
iconTextDirection(TextDirection value)
Sets item icon text direction.
iconShadows(List<ShadowMix> value)
Sets item icon shadows.
iconShadow(ShadowMix value)
Sets single item icon shadow.