RemixRemix
Actions and inputs

Select

A dropdown select component for choosing options from a list

Open catalog
Starting Flutter example…
Live Flutter from the pinned Remix component catalog. Examples include Fortal styling; the source tab shows the catalog code, not a standalone application.

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 from icon.

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 minWidth for a menu at least as wide as the trigger, width for exactly as wide, or maxWidth for 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. RemixSelect and RemixMenu build their menu styles inside the overlay, so use it in the select content and menuContainer styles or the menu overlay style. A style resolved outside the overlay, such as the trigger's or a RemixPopover's, reads 0.
  • Merging a minWidth into content replaces 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 on menuContainer: that box sits inside the padding and border of content, so the menu ends up that much wider than the trigger.
  • On a single box, a minWidth larger than its maxWidth is 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.

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.

View page source on GitHub

On this page

When to use thisAnatomyBasic implementationLong listsMatch the trigger widthFortal widgetsReduced motionConstructorPropertiesWidget Propertieskey → Key?trigger → RemixSelectTriggeritems → List<RemixSelectItem<T>>selectedValue → T?onChanged → ValueChanged<T?>?onOpen → VoidCallback?onClose → VoidCallback?enabled → boolmouseCursor → MouseCursorsemanticLabel → String?closeOnSelect → boolfocusNode → FocusNode?style → SelectStylerstyleSpec → SelectSpec?Select Style Methodscontent(SelectContentStyler value)menuContainer(FlexBoxStyler value)trigger(SelectTriggerStyler value)item(SelectMenuItemStyler value)animate(AnimationConfig value)variants(List<VariantStyle<SelectSpec>> value)foregroundDecoration(DecorationMix value)transform(Matrix4 value, AlignmentGeometry alignment = Alignment.center)wrap(WidgetModifierConfig value)modifier(WidgetModifierConfig value)call<T>({ ... })Select Trigger Style Methodscontainer(FlexBoxStyler value)label(TextStyler value)icon(IconStyler value)indicator(IconStyler value)indicatorOpacity(double value)alignment(Alignment value)constraints(BoxConstraintsMix value)decoration(DecorationMix value)margin(EdgeInsetsGeometryMix value)padding(EdgeInsetsGeometryMix value)foregroundDecoration(DecorationMix value)transform(Matrix4 value, AlignmentGeometry alignment = Alignment.center)animate(AnimationConfig value)variants(List<VariantStyle<SelectTriggerSpec>> value)wrap(WidgetModifierConfig value)modifier(WidgetModifierConfig value)labelStyle(TextStyleMix value)labelColor(Color value)labelFontSize(double value)labelFontWeight(FontWeight value)labelFontStyle(FontStyle value)labelLetterSpacing(double value)labelDecoration(TextDecoration value)labelFontFamily(String value)labelHeight(double value)labelWordSpacing(double value)labelDecorationColor(Color value)iconColor(Color value)iconSize(double value)iconOpacity(double value)iconWeight(double value)iconGrade(double value)iconFill(double value)iconOpticalSize(double value)iconBlendMode(BlendMode value)iconTextDirection(TextDirection value)iconShadows(List<ShadowMix> value)iconShadow(ShadowMix value)Select Menu Item Style Methodscontainer(FlexBoxStyler value)containerEffects(RemixBoxEffectsMix value)text(TextStyler value)label(TextStyler value)icon(IconStyler value)alignment(Alignment value)constraints(BoxConstraintsMix value)decoration(DecorationMix value)margin(EdgeInsetsGeometryMix value)padding(EdgeInsetsGeometryMix value)foregroundDecoration(DecorationMix value)transform(Matrix4 value, AlignmentGeometry alignment = Alignment.center)animate(AnimationConfig value)variants(List<VariantStyle<SelectMenuItemSpec>> value)wrap(WidgetModifierConfig value)modifier(WidgetModifierConfig value)labelStyle(TextStyleMix value)labelColor(Color value)labelFontSize(double value)labelFontWeight(FontWeight value)labelFontStyle(FontStyle value)labelLetterSpacing(double value)labelDecoration(TextDecoration value)labelFontFamily(String value)labelHeight(double value)labelWordSpacing(double value)labelDecorationColor(Color value)iconColor(Color value)iconSize(double value)iconOpacity(double value)iconWeight(double value)iconGrade(double value)iconFill(double value)iconOpticalSize(double value)iconBlendMode(BlendMode value)iconTextDirection(TextDirection value)iconShadows(List<ShadowMix> value)iconShadow(ShadowMix value)