Skip to content

DropdownMenuEntry.labelWidget is not used for the selected item #166999

Closed

Description

@matanlurey

It appears (to me) that DropdownMenuEntry.labelWidget is ignored when rendering the selected item.

What that means in practice is if I have a complex object (i.e. without a reasonable toString()), and I want to rely entirely on a provided itemBuilder, the label appears to take precedence over labelWidget.

For example, in the following custom wrapper widget, Branch is shown instead of itemBuilder(option):

Image

final class DropdownSelect<T> extends StatelessWidget {
  const DropdownSelect({
    required this.options,
    required this.itemBuilder,
    required this.selected,
    this.onSelect,
  });

  /// Options that can be selected.
  final List<T> options;

  /// Returns a [Widget] representing the item for [T].
  ///
  /// ## Example
  ///
  /// ```dart
  /// TappableSelect(
  ///   itemBuilder: (item) => Text(item.name),
  /// )
  /// ```
  final Widget Function(T) itemBuilder;

  /// Currently selected element of type [T].
  final T selected;

  /// Invoked when an item is selected.
  ///
  /// If `null` this select is read only.
  final void Function(T)? onSelect;

  @override
  Widget build(BuildContext context) {
    return DropdownMenu(
      hintText: 'Select a branch',
      dropdownMenuEntries: [
        ...options.map((option) {
          return DropdownMenuEntry(
            value: option,
            label: '',
            labelWidget: itemBuilder(option),
          );
        }),
      ],
      enableFilter: true,
      initialSelection: selected,
      onSelected: invokeIfNonNull(onSelect),
      label: Text('Branch'),
    );
  }
}

Is that WAI?

Activity

  1. added
    P2Important issues not at the top of the work list
    team-designOwned by Design Languages team
    triaged-designTriaged by Design Languages team
    and removed on Jun 5, 2025
  2. self-assigned this
    on Sep 5, 2025
  3. moved this from To do to In progress in Nevercodeon Sep 5, 2025
  4. huycozy commented on Sep 5, 2025

    @huycozy
    Member

    Here is the complete sample code, where I also added another DropdownMenu with pure String value to entries to compare with one that uses a complex widget for labelWidget:

    Sample code
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MaterialApp(home: MatanRepro()));
    }
    
    class MatanRepro extends StatefulWidget {
      const MatanRepro({super.key});
    
      @override
      State<MatanRepro> createState() => _MatanReproState();
    }
    
    class _MatanReproState extends State<MatanRepro> {
      String _selected = '';
      int _value = 1;
      final values = [1, 2, 3];
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                DropdownMenu(
                  dropdownMenuEntries: values
                      .map(
                        (vl) => DropdownMenuEntry(
                          value: vl,
                          label: vl.toString(),
                        ),
                      )
                      .toList(),
                  label: Text('label'),
                  onSelected: (value) {
                    setState(() {
                      _value = value ?? 1;
                    });
                  },
                  initialSelection: _value,
                ),
                DropdownSelect<String>(
                  options: ['A', 'B', 'C'],
                  itemBuilder: (String text) => ListTile(
                    title: Text(text),
                    leading: Icon(Icons.arrow_forward),
                    tileColor: Colors.amberAccent,
                  ),
                  selected: _selected,
                  onSelect: (String text) {
                    setState(() {
                      _selected = text;
                    });
                  },
                ),
              ],
            ),
          ),
        );
      }
    }
    
    final class DropdownSelect<T> extends StatelessWidget {
      const DropdownSelect({
        super.key,
        required this.options,
        required this.itemBuilder,
        required this.selected,
        this.onSelect,
      });
    
      /// Options that can be selected.
      final List<T> options;
    
      /// Returns a [Widget] representing the item for [T].
      ///
      /// ## Example
      ///
      /// ```dart
      /// TappableSelect(
      ///   itemBuilder: (item) => Text(item.name),
      /// )
      /// ```
      final Widget Function(T) itemBuilder;
    
      /// Currently selected element of type [T].
      final T selected;
    
      /// Invoked when an item is selected.
      ///
      /// If `null` this select is read only.
      final void Function(T)? onSelect;
    
      @override
      Widget build(BuildContext context) {
        return DropdownMenu(
          hintText: 'Select a branch',
          dropdownMenuEntries: [
            ...options.map((option) {
              return DropdownMenuEntry(
                value: option,
                label: '',
                labelWidget: itemBuilder(option),
              );
            }),
          ],
          enableFilter: true,
          initialSelection: selected,
          onSelected: (_) {},
          label: Text('Branch'),
        );
      }
    }
    
    Demo screenshot/video
    Screen.Recording.2025-09-05.at.15.13.10.mov
  5. huycozy commented on Sep 9, 2025

    @huycozy
    Member

    After looking into this, here is what I recommend, please let me know your thoughts.

    Since DropdownMenuEntry.labelWidget is a widget, that means it can be a simple or complex widget. We can't know what is inside it, it's user input.
    What is rendered in the field is governed via _effectiveTextEditingController, see:

    controller: _effectiveTextEditingController,

    I also found that there is a workaround for this: users can pass a String value to DropdownMenuEntry.label. because the field accepts it to render the selected item/entry, see:

    _effectiveTextEditingController.value = TextEditingValue(
    text: filteredEntries[index].label,
    selection: TextSelection.collapsed(offset: filteredEntries[index].label.length),

    We can either:

    1. Accept the workaround above and improve the documentation only
    2. Otherwise, I suggest adding a new property called displayText or text with String? type. This would require users to specify this property when they want the labelWidget to display only text or a partial rendering.
    Demo of the workaround
    Screen.Recording.2025-09-09.at.16.22.37.mov

    @matanlurey Please let me know if this is appropriate to your case (cc @Piinks for thoughts as well).

  6. Piinks commented on Sep 26, 2025

    @Piinks
    Contributor

    cc @QuncCccccc who can advise on the best option from your findings @huycozy Thanks!

  7. QuncCccccc commented on Oct 9, 2025

    @QuncCccccc
    Contributor

    Sorry for the late response. I would prefer the option 1. labelWidget is added to DropdownMenuEntry to improve the customizability of the options (check this example). When the menu is collapsed, the "button" is actually just a text field which accept text only. So we will not be able to show a Widget-type thing.

    Otherwise, I suggest adding a new property called displayText or text with String? type. This would require users to specify this property when they want the labelWidget to display only text or a partial rendering.

    Looks like this displayText is similar to label? If so, I think we can just add document on labelWidget and mention that label is what will be showed up in the text field when an entry is selected. WDYT?

  8. huycozy commented on Oct 10, 2025

    @huycozy
    Member

    When the menu is collapsed, the "button" is actually just a text field which accept text only. So we will not be able to show a Widget-type thing.

    @QuncCccccc Yes. I also would prefer the first option, as the original goal of labelWidget was to override the automatically created label widget(is Text(label) by default), see #133491.

    Looks like this displayText is similar to label? If so, I think we can just add document on labelWidget and mention that label is what will be showed up in the text field when an entry is selected.

    Yes, it's similar. But if we don't document label or labelWidget, users may be confused about labelWidget; they might think that a label can be rendered as what they put in(that was what Matan thought at first look, wasn't it). So, I was considering introducing an obvious property for that. Now that I re-think about it, this might be redundant. I will go with the first option. Thanks for looking at this.

  9. moved this from In progress to PR submitted in Nevercodeon Oct 17, 2025
  10. moved this from PR submitted to Done (PR merged) in Nevercodeon Oct 28, 2025
  11. added
    r: fixedIssue is closed as already fixed in a newer version
    on Oct 28, 2025
  12. github-actions commented on Nov 11, 2025

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  13. locked as resolved and limited conversation to collaborators on Nov 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P2Important issues not at the top of the work listp: material_uimaterial_ui package in flutter/packagesr: fixedIssue is closed as already fixed in a newer versionteam-designOwned by Design Languages teamtriaged-designTriaged by Design Languages team

Type

No type

Projects

  • Status
    Done (PR merged)

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions