RemixRemix
Navigation

Menu

A dropdown menu component for displaying a list of actions or options

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 menu component for displaying actions or options in an overlay.

When to use this

  • Action menus: Show a list of available actions for a selected item
  • Context menus: Provide contextual options based on user selection
  • Navigation: Create dropdown navigation menus
  • Settings: Display configuration options in a compact dropdown

Basic implementation

Basic implementation

import 'package:flutter/material.dart';
import 'package:remix/remix.dart';

class MenuExample extends StatefulWidget {
  const MenuExample({super.key});

  @override
  State<MenuExample> createState() => _MenuExampleState();
}

class _MenuExampleState extends State<MenuExample> {
  final controller = MenuController();

  @override
  Widget build(BuildContext context) {
    return RemixMenu<String>(
      trigger: const RemixMenuTrigger(label: 'Open Menu'),
      items: [
        RemixMenuItem(
          value: 'History',
          leadingIcon: Icons.history,
          label: 'History',
        ),
        RemixMenuItem(
          value: 'Settings',
          leadingIcon: Icons.settings,
          label: 'Settings',
        ),
        const RemixMenuDivider(),
        RemixMenuItem(
          value: 'Logout',
          leadingIcon: Icons.logout,
          label: 'Logout',
          style: MenuItemStyler().onHovered(
            MenuItemStyler()
                .color(Colors.redAccent.withValues(alpha: 0.05))
                .label(TextStyler().color(Colors.redAccent))
                .leadingIcon(IconStyler().color(Colors.redAccent)),
          ),
        ),
      ],
      positioning: const OverlayPositionConfig(
        sideOffset: 8,
        side: OverlaySide.bottom,
        alignment: OverlayAlignment.center,
      ),
      style: menuStyle,
      onSelected: (value) {
        debugPrint('Selected: $value');
      },
      controller: controller,
    );
  }

  MenuStyler get menuStyle {
    return MenuStyler()
        .trigger(
          MenuTriggerStyler()
              .padding(EdgeInsetsMix.symmetric(horizontal: 14))
              .decoration(
                BoxDecorationMix()
                    .color(Colors.white)
                    .borderRadius(BorderRadiusMix.all(const Radius.circular(12)))
                    .border(BorderMix.all(BorderSideMix(color: Colors.blueGrey.shade100)))
                    .boxShadow([
                  BoxShadowMix(
                    color: Colors.blueGrey.withValues(alpha: 0.1),
                    blurRadius: 3,
                    offset: const Offset(0, 3),
                  ),
                ]),
              )
              .constraints(BoxConstraintsMix(minHeight: 40))
              .label(
                TextStyler()
                    .color(Colors.blueGrey.shade700)
                    .fontWeight(FontWeight.w400),
              ),
        )
        .overlay(
          FlexBoxStyler(
            padding: EdgeInsetsMix.all(12),
            decoration: BoxDecorationMix(
              color: Colors.white,
              borderRadius: BorderRadiusMix.all(const Radius.circular(12)),
              border: BorderMix.all(BorderSideMix(color: Colors.blueGrey.shade100)),
              boxShadow: [
                BoxShadowMix(
                  color: Colors.blueGrey.withValues(alpha: 0.1),
                  blurRadius: 3,
                  offset: const Offset(0, 3),
                ),
              ],
            ),
          ),
        )
        .item(menuItemStyle);
  }

  MenuItemStyler get menuItemStyle {
    return MenuItemStyler()
        .padding(.all(6))
        .leadingIcon(IconStyler().size(20).color(Colors.blueGrey.shade800))
        .spacing(8)
        .borderRadius(.all(const Radius.circular(8)))
        .label(TextStyler().color(Colors.blueGrey.shade800))
        .onHovered(MenuItemStyler().color(Colors.blueGrey.shade50));
  }
}

An item without a label is icon-only and must set semanticLabel, the name screen readers announce.

Custom trigger content

RemixMenuTrigger is a configuration object, not a widget. The same object is the trigger for FortalMenu; there is no separate Fortal trigger type. It has three constructors:

  • The unnamed constructor is the standard label/icon surface.
  • RemixMenuTrigger.builder is custom visual content, such as an avatar or profile row. The builder receives the current menu state and the already styled default trigger as defaultTrigger. It may wrap that child or replace it. NakedMenu owns tapping, focus, keyboard behavior, and expanded state.
  • RemixMenuTrigger.control is a real button, such as a FortalButton. The button owns its look, label, enabled state, and activation.

Builder output must be non-interactive visual content. Returning a FortalButton, FortalIconButton, or any other nested interactive control from RemixMenuTrigger.builder creates duplicate semantics and a second tap target. Use RemixMenuTrigger.control when the trigger is a button.

Builder-backed avatar trigger

import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
import 'ui/ui.dart';

class AccountMenuExample extends StatelessWidget {
  const AccountMenuExample({super.key});

  @override
  Widget build(BuildContext context) {
    return FortalMenu<String>(
      trigger: RemixMenuTrigger.builder(
        label: 'Account menu',
        // The builder replaces the styled trigger, so it draws its own ring.
        builder: (context, state, defaultTrigger) => RemixCard(
          style: CardStyler()
              .borderRadius(.circular(999))
              .onFocusVisible(
                CardStyler().containerEffects(
                  RemixBoxEffectsMix(
                    outline: BorderSideMix(color: Colors.indigo, width: 2),
                    outlineOffset: 2,
                  ),
                ),
              ),
          child: const FortalAvatar(label: 'LF'),
        ),
      ),
      items: const [
        RemixMenuItem(value: 'profile', label: 'View profile'),
        RemixMenuItem(value: 'preferences', label: 'Preferences'),
        RemixMenuDivider(),
        RemixMenuItem(value: 'signout', label: 'Sign out'),
      ],
      onSelected: (value) => debugPrint(value),
    );
  }
}

Use a button as the trigger

RemixMenuTrigger.control takes a builder that receives a RemixMenuTriggerHandle. Pass the handle's focusNode and toggle to your button as its focusNode and onPressed.

A button as the trigger

import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
import 'ui/ui.dart';

class ActionsMenuExample extends StatelessWidget {
  const ActionsMenuExample({super.key});

  @override
  Widget build(BuildContext context) {
    return FortalMenu<String>(
      trigger: RemixMenuTrigger.control(
        builder: (context, menu) => FortalButton(
          label: 'Actions',
          trailingIcon: Icons.keyboard_arrow_down,
          focusNode: menu.focusNode,
          onPressed: menu.toggle,
        ),
      ),
      items: const [
        RemixMenuItem(value: 'edit', label: 'Edit'),
        RemixMenuItem(value: 'duplicate', label: 'Duplicate'),
        RemixMenuDivider(),
        RemixMenuItem(value: 'delete', label: 'Delete'),
      ],
      onSelected: (value) => debugPrint(value),
    );
  }
}

The handle exposes:

  • isOpen: whether the menu is open.
  • controller: the MenuController that opens and closes the menu. It is the controller you passed to the menu, or one the menu creates.
  • focusNode: the node that receives focus when the menu closes; on open, focus moves into the menu panel. It is the triggerFocusNode you passed to the menu, or a node the menu creates and disposes.
  • toggle(): opens a closed menu and closes an open one.

The menu keeps the overlay, Escape, outside taps, focus return to the button, and the expanded state, which it merges onto the button's own semantics node so a screen reader announces one expandable button. The button keeps its look, accessible name, enabled state, and Enter and Space activation. Pass onPressed: null to disable it; a disabled button does not open the menu.

The builder must return a single control. Its expanded state stays on the button, while a loading button's live announcement remains a separate node. A split button needs separate menu controls.

MenuStyler.trigger and RemixMenu.semanticLabel do not apply to a control trigger, because the button owns its look and name. Setting semanticLabel on a menu with a control trigger fails an assertion.

Checkboxes, radio groups, and submenus

Checkboxes and radio groups are controlled. Keep their current value in your widget state and update that state from onChanged. Submenus can contain any menu item data, including another submenu.

Controlled compound items

import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
import 'ui/ui.dart';

class CompoundMenuExample extends StatefulWidget {
  const CompoundMenuExample({super.key});

  @override
  State<CompoundMenuExample> createState() => _CompoundMenuExampleState();
}

class _CompoundMenuExampleState extends State<CompoundMenuExample> {
  bool showStatus = true;
  String density = 'comfortable';

  @override
  Widget build(BuildContext context) {
    return FortalMenu<String>.soft(
      trigger: const RemixMenuTrigger(label: 'View options'),
      onSelected: (value) => debugPrint('Root selected: $value'),
      items: [
        RemixMenuCheckboxItem(
          value: 'show-status',
          label: 'Show status',
          checked: showStatus,
          closeOnActivate: false,
          onChanged: (next) => setState(() => showStatus = next),
        ),
        RemixMenuRadioGroup(
          value: density,
          onChanged: (next) => setState(() => density = next),
          items: const [
            RemixMenuRadioItem(
              value: 'compact',
              label: 'Compact',
              closeOnActivate: false,
            ),
            RemixMenuRadioItem(
              value: 'comfortable',
              label: 'Comfortable',
              closeOnActivate: false,
            ),
          ],
        ),
        const RemixMenuDivider(),
        const RemixMenuSubmenu(
          label: 'Share',
          items: [
            RemixMenuItem(value: 'copy-link', label: 'Copy link'),
            RemixMenuSubmenu(
              label: 'More',
              items: [
                RemixMenuItem(value: 'email', label: 'Email'),
              ],
            ),
          ],
        ),
      ],
    );
  }
}

RemixMenuCheckboxItem.checked and RemixMenuRadioGroup.value are never changed internally. On activation, RemixMenu.onSelected runs first, followed by the checkbox or radio-group onChanged. Selecting the already-selected radio item re-emits its value. A checkbox or radio item is disabled when neither its local callback nor the root onSelected callback is supplied.

closeOnActivate defaults to true. Set it to false for settings that should remain visible while they are changed. Submenus open after a 100 ms hover delay by default. The directional forward-arrow opens a focused submenu, the reverse arrow closes it, and Escape closes it and restores focus to its trigger. These directions are mirrored automatically in right-to-left layouts.

KeyBehavior
Down / Up arrowMove focus between enabled items
Enter / SpaceActivate the focused item
Right arrow in LTR / Left arrow in RTLOpen the focused submenu and focus its first item
Left arrow in LTR / Right arrow in RTLClose the current submenu and focus its trigger
EscapeClose the current menu level and restore trigger focus

The root items list, each radio group's items, and every submenu's items are copied to immutable snapshots when their panel is built. Rebuild the menu after changing a source list; do not rely on mutating a visible panel in place.

For dynamic menus, including lists that are filtered, have items inserted, or are reordered, supply a stable caller-owned key: on each ordinary item, checkbox item, radio group, radio item, submenu, and divider. Build keys from application-owned item identity and keep them stable across rebuilds. Item keys are not derived automatically from labels or values.

Compound-item styling

Compound rows use the same MenuItemStyler as ordinary rows, including hovered, focused, pressed, disabled, and selected-state variants. An open submenu trigger contributes WidgetState.selected.

import 'package:flutter/material.dart';
import 'package:remix/remix.dart';

final sharedItemStyle = MenuItemStyler()
    .padding(.horizontal(12))
    .spacing(8)
    .onSelected(.color(Colors.indigo.withValues(alpha: 0.08)));

final choiceItemStyle = MenuItemStyler().indicator(
  IconStyler().size(9).color(Colors.indigo),
);

final appMenuStyle = MenuStyler()
    .item(sharedItemStyle)
    .checkboxItem(choiceItemStyle)
    .radioItem(choiceItemStyle)
    .submenuItem(
      MenuItemStyler().trailingIcon(IconStyler().color(Colors.indigo)),
    );

item is the shared foundation for every actionable row. The semantic checkboxItem, radioItem, and submenuItem styles merge after it, and an individual item's style merges last. RemixMenuRadioGroup is behavioral and does not add another styling layer. Use the normal MenuItemStyler flex, padding, and spacing methods for layout.

The renderer reserves one leading choice slot for every row in a panel when that panel directly contains a checkbox or radio group. Nested submenu panels decide this independently. A custom trailingIcon replaces the default directional submenu chevron.

Shortcuts and counts

trailingText adds a short string after an item's label, such as a keyboard shortcut or a count. Items, checkbox items, radio items, and submenus accept it.

import 'package:flutter/material.dart';
import 'package:remix/remix.dart';

final appMenu = RemixMenu<String>(
  trigger: const RemixMenuTrigger(label: 'More'),
  style: MenuStyler()
      .overlay(FlexBoxStyler().minWidth(200))
      .item(
        MenuItemStyler()
            .spacing(8)
            .label(TextStyler().fontSize(13).color(Colors.black87))
            .trailingText(TextStyler().fontSize(12).color(Colors.black45)),
      ),
  onSelected: (value) => debugPrint(value),
  items: const [
    RemixMenuItem(value: 'refresh', label: 'Refresh', trailingText: '⌘R'),
    RemixMenuItem(value: 'settings', label: 'Settings…', trailingText: '⌘,'),
    RemixMenuItem(
      value: 'shortcuts',
      label: 'Keyboard shortcuts',
      trailingText: '?',
    ),
  ],
);

The text appears before any trailing icon or submenu chevron. A minimum panel width, as above, leaves room between the longest label and its text. It inherits the label's text style, so style only what differs, such as a muted color. Screen readers announce it after the label unless the item sets semanticLabel. trailingText only displays a shortcut; bind the key with Shortcuts or CallbackShortcuts.

Long menus

The panel scrolls when its items are taller than the room 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 menu scrolls there instead of covering the trigger. Every submenu panel scrolls the same way, within the room the overlay has. A maxHeight on the overlay style caps the panel lower still. A menu that fits keeps the size of its items, and the overlay's padding, decoration, and effects stay in place while the items 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() {
  MenuStyler().overlay(FlexBoxStyler().maxHeight(320));
}

Match the trigger width

A menu panel is as wide as its widest row. remixAnchorWidth is the width of the trigger that opened the menu, so the panel can follow it, like CSS min-width: var(--trigger-width):

import 'package:flutter/material.dart';
import 'package:remix/remix.dart';

class TriggerWidthMenuExample extends StatelessWidget {
  const TriggerWidthMenuExample({super.key});

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: 240,
      child: RemixMenu<String>(
        trigger: const RemixMenuTrigger(label: 'Actions'),
        style: MenuStyler().overlay(
          FlexBoxStyler().minWidth(remixAnchorWidth()),
        ),
        items: const [
          RemixMenuItem(value: 'copy', label: 'Copy'),
          RemixMenuItem(value: 'paste', label: 'Paste'),
        ],
        onSelected: (value) => debugPrint(value),
      ),
    );
  }
}
  • Pass it to minWidth for a panel at least as wide as the trigger, width for exactly as wide, or maxWidth for no wider. The panel follows the trigger if it resizes while open.
  • It is opt-in. Without it the panel keeps the width its styles give it.
  • The token resolves where the style is built. RemixMenu and RemixSelect build their menu styles inside the overlay, so it works in the menu overlay style. A style resolved outside the overlay, such as the trigger's or a RemixPopover's, reads 0.
  • Submenu panels reuse the style resolved for the root panel, so they take the root trigger's width.
  • 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 panel wider than its trigger starts at the trigger's start edge by default, and follows the text direction.

Focus rings

MenuTriggerStyler.containerEffects and MenuItemStyler.containerEffects paint an outline around the trigger or a row without taking layout space, so focus does not resize either. MenuStyler.containerEffects is separate: it paints the popup panel.

import 'package:flutter/material.dart';
import 'package:remix/remix.dart';

final menuRing = RemixBoxEffectsMix(
  outline: BorderSideMix(color: Colors.indigo, width: 2),
  outlineOffset: 2,
);

final ringedMenuStyle = MenuStyler()
    .trigger(
      MenuTriggerStyler().onFocusVisible(
        MenuTriggerStyler().containerEffects(menuRing),
      ),
    )
    .item(
      MenuItemStyler().onFocusVisible(
        MenuItemStyler().containerEffects(menuRing),
      ),
    );

A popup that clips (clipBehavior on overlay) crops an outside row ring at its edge. Pad the popup by at least the ring's offset plus width, or use a negative outlineOffset to draw the row ring inside the row. Once the items are long enough to scroll, the list clips at its own edges, inside the popup's padding, so only an inside ring stays whole.

A RemixMenuTrigger.builder that returns a different widget instead of its child replaces the styled trigger and its effects, so the returned content has to draw its own focus ring, as the avatar example above does. A builder runs under the menu's widget states, so onFocusVisible on its styles works.

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 FortalMenuExample extends StatelessWidget {
  const FortalMenuExample({super.key});

  @override
  Widget build(BuildContext context) {
    final choiceItemStyle = MenuItemStyler().indicator(
      IconStyler().color(Colors.indigo),
    );
    final customStyle = MenuStyler()
        .checkboxItem(choiceItemStyle)
        .radioItem(choiceItemStyle);
    final items = <RemixMenuItemData<String>>[
      const RemixMenuItem(
        value: 'edit',
        label: 'Edit',
        leadingIcon: Icons.edit,
      ),
      const RemixMenuCheckboxItem(
        value: 'show-status',
        label: 'Show status',
        checked: true,
      ),
      const RemixMenuRadioGroup(
        value: 'comfortable',
        items: [
          RemixMenuRadioItem(value: 'compact', label: 'Compact'),
          RemixMenuRadioItem(value: 'comfortable', label: 'Comfortable'),
        ],
      ),
    ];

    return Row(
      spacing: 16,
      children: [
        RemixMenu<String>(
          style: fortalMenuStyle(
            variant: FortalMenuVariant.solid,
          ).merge(customStyle),
          trigger: const RemixMenuTrigger(label: 'Solid', icon: Icons.menu),
          items: items,
          onSelected: (value) => debugPrint(value),
        ),
        RemixMenu<String>(
          style: fortalMenuStyle(
            variant: FortalMenuVariant.soft,
          ).merge(customStyle),
          trigger: const RemixMenuTrigger(label: 'Soft', icon: Icons.menu),
          items: items,
          onSelected: (value) => debugPrint(value),
        ),
      ],
    );
  }
}

The generated FortalMenu, FortalMenu.solid, and FortalMenu.soft constructors apply only the selected recipe. For reusable menu-wide customization, pass fortalMenuStyle(...).merge(customStyle) to RemixMenu.style; the custom fields merge after the recipe.

Fortal uses the pinned check and submenu-chevron paths, colors, sizes, and states. Their placement uses Remix's standard leading flex slot and item spacing, so small indicator and trailing-gap differences are the documented visual tolerance.

See the fortalMenuStyle source code for all available options.

Constructor

Constructor

import 'package:flutter/material.dart';
import 'package:remix/remix.dart';

RemixMenu<T> remixMenuConstructor<T>({
  Key? key,
  required RemixMenuTrigger trigger,
  required List<RemixMenuItemData<T>> items,
  MenuController? controller,
  ValueChanged<T>? onSelected,
  VoidCallback? onOpen,
  VoidCallback? onClose,
  VoidCallback? onCanceled,
  RawMenuAnchorOpenRequestedCallback? onOpenRequested,
  RawMenuAnchorCloseRequestedCallback? onCloseRequested,
  bool consumeOutsideTaps = true,
  bool useRootOverlay = false,
  bool closeOnClickOutside = true,
  FocusNode? triggerFocusNode,
  OverlayPositionConfig positioning = const OverlayPositionConfig(),
  String? semanticLabel,
  bool excludeSemantics = false,
  MenuStyler style = const MenuStyler.create(),
  MenuSpec? styleSpec,
}) => throw UnimplementedError();

Properties

Widget Properties

key → Key?

Optional. Controls how one widget replaces another widget in the tree.

trigger → RemixMenuTrigger

Required. Configuration for the menu's button, not a widget. Use RemixMenuTrigger(label:, icon:) for the standard surface, RemixMenuTrigger.builder(label:, icon:, builder:) for custom visual content, or RemixMenuTrigger.control(builder:) for a real button. label is the visible default label and the accessible fallback for a builder-backed trigger; it is null for a control trigger. The same object works with FortalMenu.

items → List<RemixMenuItemData<T>>

Required. The list of ordinary items, checkbox items, radio groups, submenus, and dividers.

controller → MenuController?

Optional. Optional controller for programmatic control of the menu state. If not provided, an internal controller will be created automatically.

onSelected → ValueChanged<T>?

Optional. Called when an item is selected.

onOpen → VoidCallback?

Optional. Called when the menu opens.

onClose → VoidCallback?

Optional. Called when the menu closes.

onCanceled → VoidCallback?

Optional. Called when the menu closes without a selection.

onOpenRequested → RawMenuAnchorOpenRequestedCallback?

Optional. Open/close interceptors (for example, to drive animations).

onCloseRequested → RawMenuAnchorCloseRequestedCallback?

Optional.

consumeOutsideTaps → bool

Optional. Whether outside taps on the trigger are consumed.

useRootOverlay → bool

Optional. Whether to target the root overlay instead of the nearest ancestor.

closeOnClickOutside → bool

Optional. Whether taps outside the overlay close the menu.

triggerFocusNode → FocusNode?

Optional. Optional focus node for the trigger. A RemixMenuTrigger.control receives it as menu.focusNode; without one the menu creates and disposes a node for the control.

positioning → OverlayPositionConfig

Optional. Overlay positioning configuration.

semanticLabel → String?

Optional. The accessible label for the menu trigger. Must be null with RemixMenuTrigger.control, whose button owns its label.

excludeSemantics → bool

Optional. Whether to hide the menu trigger from the semantics tree. Defaults to false. This applies to the trigger only — the overlay is mounted separately, so an open menu keeps announcing its items.

style → MenuStyler

Optional. The style configuration for the menu.

styleSpec → MenuSpec?

Optional. A raw resolved style spec that bypasses fluent style resolution. For semantic item fields, null inherits the raw item spec and a supplied value completely replaces it; per-item fluent styles remain bypassed.

trigger(MenuTriggerStyler value)

Configures the trigger button style.

overlay(FlexBoxStyler value)

Configures the menu overlay container style. Its width, minWidth, and maxWidth accept remixAnchorWidth() to follow the trigger width.

containerEffects(RemixBoxEffectsMix value)

Configures the outline and paint layers drawn around the popup panel without affecting layout.

item(MenuItemStyler value)

Configures the shared default for every actionable menu row.

checkboxItem(MenuItemStyler value)

Configures the menu-wide checkbox-row override after item.

radioItem(MenuItemStyler value)

Configures the menu-wide radio-row override after item.

Configures the menu-wide submenu-trigger override after item.

divider(DividerStyler value)

Configures divider style.

animate(AnimationConfig value)

Configures implicit animation for style transitions.

variants(List<VariantStyle<MenuSpec>> value)

Sets style variants.

wrap(WidgetModifierConfig value)

Applies widget modifiers such as clipping, opacity, or scaling.

modifier(WidgetModifierConfig value)

Sets the widget modifier.

call<T>({..., String? semanticLabel, bool excludeSemantics = false})

Creates a RemixMenu widget with this style applied.

container(FlexBoxStyler value)

Configures the trigger container style.

containerEffects(RemixBoxEffectsMix value)

Configures the outline and paint layers drawn around the trigger without affecting layout, such as a focus ring.

label(TextStyler value)

Configures the trigger label style.

icon(IconStyler value)

Configures the trigger icon style.

animate(AnimationConfig value)

Configures implicit animation for trigger style transitions.

variants(List<VariantStyle<MenuTriggerSpec>> 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

container(FlexBoxStyler value)

Configures the item container style.

containerEffects(RemixBoxEffectsMix value)

Configures the outline and paint layers drawn around the row without affecting layout, such as a focus ring.

label(TextStyler value)

Configures the label text style using a TextStyler.

leadingIcon(IconStyler value)

Configures the leading icon style.

trailingIcon(IconStyler value)

Configures the trailing icon style.

trailingText(TextStyler value)

Configures the trailing text style, which inherits the label's.

indicator(IconStyler value)

Configures the checkbox and radio indicator style.

alignment(Alignment value)

Sets container alignment

padding(EdgeInsetsGeometryMix value)

Sets the container padding.

spacing(double value)

Sets the gap between the row's indicator, icons, label, and trailing text.

color(Color value)

Sets background color.

size(double width, double height)

Sets the component size.

borderRadius(BorderRadiusGeometryMix radius)

Sets the border radius.

constraints(BoxConstraintsMix value)

Sets size constraints on the component.

decoration(DecorationMix value)

Sets the container decoration.

margin(EdgeInsetsGeometryMix value)

Sets the container margin.

foregroundDecoration(DecorationMix value)

Sets a foreground decoration painted on top of the component.

transform(Matrix4 value, AlignmentGeometry alignment = Alignment.center)

Applies a matrix transformation to the component.

animate(AnimationConfig value)

Configures implicit animation for style transitions.

variants(List<VariantStyle<MenuItemSpec>> value)

Sets item style variants.

wrap(WidgetModifierConfig value)

Applies widget modifiers such as clipping, opacity, or scaling.

modifier(WidgetModifierConfig value)

Sets the item widget modifier.

View page source on GitHub

On this page

When to use thisBasic implementationCustom trigger contentUse a button as the triggerCheckboxes, radio groups, and submenusCompound-item stylingShortcuts and countsLong menusMatch the trigger widthFocus ringsFortal widgetsConstructorPropertiesWidget Propertieskey → Key?trigger → RemixMenuTriggeritems → List<RemixMenuItemData<T>>controller → MenuController?onSelected → ValueChanged<T>?onOpen → VoidCallback?onClose → VoidCallback?onCanceled → VoidCallback?onOpenRequested → RawMenuAnchorOpenRequestedCallback?onCloseRequested → RawMenuAnchorCloseRequestedCallback?consumeOutsideTaps → booluseRootOverlay → boolcloseOnClickOutside → booltriggerFocusNode → FocusNode?positioning → OverlayPositionConfigsemanticLabel → String?excludeSemantics → boolstyle → MenuStylerstyleSpec → MenuSpec?Menu Style Methodstrigger(MenuTriggerStyler value)overlay(FlexBoxStyler value)containerEffects(RemixBoxEffectsMix value)item(MenuItemStyler value)checkboxItem(MenuItemStyler value)radioItem(MenuItemStyler value)submenuItem(MenuItemStyler value)divider(DividerStyler value)animate(AnimationConfig value)variants(List<VariantStyle<MenuSpec>> value)wrap(WidgetModifierConfig value)modifier(WidgetModifierConfig value)call<T>({..., String? semanticLabel, bool excludeSemantics = false})Menu Trigger Style Methodscontainer(FlexBoxStyler value)containerEffects(RemixBoxEffectsMix value)label(TextStyler value)icon(IconStyler value)animate(AnimationConfig value)variants(List<VariantStyle<MenuTriggerSpec>> 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)Menu Item Style Methodscontainer(FlexBoxStyler value)containerEffects(RemixBoxEffectsMix value)label(TextStyler value)leadingIcon(IconStyler value)trailingIcon(IconStyler value)trailingText(TextStyler value)indicator(IconStyler value)alignment(Alignment value)padding(EdgeInsetsGeometryMix value)spacing(double value)color(Color value)size(double width, double height)borderRadius(BorderRadiusGeometryMix radius)constraints(BoxConstraintsMix value)decoration(DecorationMix value)margin(EdgeInsetsGeometryMix value)foregroundDecoration(DecorationMix value)transform(Matrix4 value, AlignmentGeometry alignment = Alignment.center)animate(AnimationConfig value)variants(List<VariantStyle<MenuItemSpec>> value)wrap(WidgetModifierConfig value)modifier(WidgetModifierConfig value)