Repository: MudBlazor/MudBlazor (2026-03-30T18:40:50Z)

Issue:
MudSelect MultiSelection checkboxes not changing state when not explicitly setting the MudSelectItems display value
### Before reporting

- [x] I searched existing issues
- [x] I tried in private/incognito browser

### What went wrong?

When using the MudSelect component with MultiSelection=true and you don't set the display text value for each MudSelectItem the checkbox selection state does not change until the drop down is closed and re opened. Explicitly setting the display text value is not required and is taken care of by the ToStringFunc for when I use complex objects or enums.

### Reproduction link

https://try.mudblazor.com/snippet/QuGqEHREfsBqFHxK

### Reproduction steps

1.Click on one of the mud select components (titled -- Broken --)
2.Select values and you can see the multi selection text is being updated but nothing gets "checked".
3.Close the drop down and re open it to see the values selected or "checked".


### Version (bug)

v9.2.0

### Version (working)

v8.15.0

### Environment

_No response_

### Blazor rendering mode

Server


Discussion:
@ScarletKuro: I found 1 more [situation](https://try.mudblazor.com/snippet/GEQUEouJVrAMTzSF):  MultiSelection + Without render fragements
Checkboxes are only updated after opening the `MudSelect`.
> [@ScarletKuro](https://github.com/ScarletKuro): I found 1 more [situation](https://try.mudblazor.com/snippet/GEQUEouJVrAMTzSF): MultiSelection + Without render fragements Checkboxes are only updated after opening the `MudSelect`.

Does it work in dev tho?
> Does it work in dev tho?

Sorry, forgot to mention, is happening in dev.
Ok, I will check
> Sorry, forgot to mention, is happening in dev.

You said it doesn't work in dev? Just copy pasted your code in the viewer:

![Image](https://github.com/user-attachments/assets/4e0ac286-11b0-4810-ab6b-9dc78443b3f6)
TryMud refuses to show the changes: https://try.mudblazor.com/snippet/GEQUEouJVrAMTzSF
Example is from the docs.
> TryMud refuses to show the changes: https://try.mudblazor.com/snippet/GEQUEouJVrAMTzSF Example is from the docs.

But try mud is always against the stable version not dev. Never was. There is a version displayed in the corner below.
The version with the fix will be out only today.
> But try mud is always against the stable version not dev. Never was. There is a version displayed in the corner below.
> The version with the fix will be out only today.

ah yes, should've known that by now.
Sorry for the inconvenience.



Patch:
diff --git a/src/MudBlazor/Components/Select/MudSelect.razor.cs b/src/MudBlazor/Components/Select/MudSelect.razor.cs
index ee030d057e03..aca987d8d64a 100644
--- a/src/MudBlazor/Components/Select/MudSelect.razor.cs
+++ b/src/MudBlazor/Components/Select/MudSelect.razor.cs
@@ -583,7 +583,7 @@ await SetCustomizedTextAsync(
                 // Early exit if unchanged
                 if (comparer.Equals(ReadValue, value))
                 {
-                    await _selectedValuesState.SetValueAsync(new HashSet<T?>(_selectedValues, comparer));
+                    await UpdateSelectedValuesStateAsync(comparer);
                     return;
                 }
 
@@ -600,7 +600,7 @@ await SetCustomizedTextAsync(
                 await HighlightItemForValueAsync(value);
             }
 
-            await _selectedValuesState.SetValueAsync(new HashSet<T?>(_selectedValues, comparer));
+            await UpdateSelectedValuesStateAsync(comparer);
 
             FieldChanged(_selectedValues);
 
@@ -703,7 +703,7 @@ public async Task ClearAsync()
             _selectedValues.Clear();
             await BeginValidateAsync();
             StateHasChanged();
-            await _selectedValuesState.SetValueAsync(new HashSet<T?>(_selectedValues, Comparer));
+            await UpdateSelectedValuesStateAsync();
             FieldChanged(_selectedValues);
         }
 
@@ -745,7 +745,7 @@ private async Task OnComparerChangedAsync(ParameterChangedEventArgs<IEqualityCom
         {
             // Apply comparer and refresh selected values
             _selectedValues = new HashSet<T?>(_selectedValues, arg.Value);
-            await _selectedValuesState.SetValueAsync(new HashSet<T?>(_selectedValues, arg.Value));
+            await UpdateSelectedValuesStateAsync(arg.Value);
         }
 
         private async Task OnSelectedValuesChangedAsync(ParameterChangedEventArgs<IReadOnlyCollection<T?>?> arg)
@@ -935,7 +935,7 @@ await SetCustomizedTextAsync(string.Join(Delimiter, _selectedValues.Select(Conve
             UpdateSelectAllChecked();
             _selectedValues = selectedValues; // need to force selected values because Blazor overwrites it under certain circumstances due to changes of Text or Value
             await BeginValidateAsync();
-            await _selectedValuesState.SetValueAsync(new HashSet<T?>(_selectedValues, Comparer));
+            await UpdateSelectedValuesStateAsync();
             FieldChanged(_selectedValues);
 
             if (MultiSelection && typeof(T) == typeof(string))
@@ -1156,7 +1156,7 @@ private async Task SelectAndHighlightItemAsync(MudSelectItem<T> item)
                 _selectedValues.Clear();
                 _selectedValues.Add(item.Value);
                 await SetValueAndUpdateTextAsync(item.Value, updateText: true);
-                await _selectedValuesState.SetValueAsync(new HashSet<T?>(_selectedValues, Comparer));
+                await UpdateSelectedValuesStateAsync();
             }
 
             await HighlightItemAsync(item);
@@ -1436,11 +1436,17 @@ protected async ValueTask SelectClearButtonClickHandlerAsync(MouseEventArgs e)
             _selectedValues.Clear();
             await BeginValidateAsync();
             StateHasChanged();
-            await _selectedValuesState.SetValueAsync(new HashSet<T?>(_selectedValues, Comparer));
+            await UpdateSelectedValuesStateAsync();
             FieldChanged(_selectedValues);
             await OnClearButtonClick.InvokeAsync(e);
         }
 
+        private async Task UpdateSelectedValuesStateAsync(IEqualityComparer<T?>? comparer = null)
+        {
+            await _selectedValuesState.SetValueAsync(new HashSet<T?>(_selectedValues, comparer ?? Comparer));
+            await _context.NotifySelectionChangedAsync();
+        }
+
         protected async Task SetCustomizedTextAsync(string text, bool updateValue = true,
             IReadOnlyList<string?>? selectedConvertedValues = null,
             Func<IReadOnlyList<string?>?, string>? multiSelectionTextFunc = null)
diff --git a/src/MudBlazor/Components/Select/MudSelectItem.razor.cs b/src/MudBlazor/Components/Select/MudSelectItem.razor.cs
index 6393e6a1a41a..a8ae07f5f1ef 100644
--- a/src/MudBlazor/Components/Select/MudSelectItem.razor.cs
+++ b/src/MudBlazor/Components/Select/MudSelectItem.razor.cs
@@ -212,11 +212,6 @@ private async Task OnSelectionChangedAsync(IReadOnlyCollection<T?> selectedValue
         /// </summary>
         private async Task OnClickHandleAsync()
         {
-            if (MultiSelection)
-            {
-                Selected = !Selected;
-            }
-
             if (MudSelect is not null)
             {
                 await MudSelect.SelectOption(Value);


Test patch:
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectChildlessEnumToStringFuncTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectChildlessEnumToStringFuncTest.razor
new file mode 100644
index 000000000000..7f9cafa67c1c
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectChildlessEnumToStringFuncTest.razor
@@ -0,0 +1,24 @@
+<MudPopoverProvider />
+
+<MudSelect T="Pizza" MultiSelection="true" ToStringFunc="GetDisplayText" @bind-SelectedValues="_selected">
+    @foreach (var item in Enum.GetValues<Pizza>())
+    {
+        <MudSelectItem T="Pizza" Value="@item" />
+    }
+</MudSelect>
+
+@code {
+    public static string __description__ = "Multi-selection with childless enum items should refresh checkbox state immediately when ToStringFunc provides the display text.";
+
+    private IReadOnlyCollection<Pizza> _selected = [];
+
+    public enum Pizza
+    {
+        Cardinale,
+        Diavolo,
+        Margarita,
+        Spinaci
+    }
+
+    private static string? GetDisplayText(Pizza value) => Enum.GetName(value);
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectChildlessSelectAllTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectChildlessSelectAllTest.razor
new file mode 100644
index 000000000000..abf33948939e
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectChildlessSelectAllTest.razor
@@ -0,0 +1,15 @@
+<MudPopoverProvider />
+
+<MudSelect T="string" MultiSelection="true" SelectAll="true" @bind-SelectedValues="_selected">
+    @foreach (var item in _items)
+    {
+        <MudSelectItem T="string" Value="@item" />
+    }
+</MudSelect>
+
+@code {
+    public static string __description__ = "Select-all should refresh checkbox state immediately for childless multi-select items.";
+
+    private IReadOnlyCollection<string> _selected = [];
+    private readonly string[] _items = ["Cardinale", "Diavolo", "Margarita", "Spinaci"];
+}
diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectChildlessStringTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectChildlessStringTest.razor
new file mode 100644
index 000000000000..4746f64c587d
--- /dev/null
+++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectChildlessStringTest.razor
@@ -0,0 +1,15 @@
+<MudPopoverProvider />
+
+<MudSelect T="string" MultiSelection="true" @bind-SelectedValues="_selected">
+    @foreach (var item in _items)
+    {
+        <MudSelectItem T="string" Value="@item" />
+    }
+</MudSelect>
+
+@code {
+    public static string __description__ = "Multi-selection with childless string items should refresh checkbox state immediately without explicit item text.";
+
+    private IReadOnlyCollection<string> _selected = [];
+    private readonly string[] _items = ["Cardinale", "Diavolo", "Margarita", "Spinaci"];
+}
diff --git a/src/MudBlazor.UnitTests/Components/SelectTests.cs b/src/MudBlazor.UnitTests/Components/SelectTests.cs
index b4e732b2af46..c7e12d8ce224 100644
--- a/src/MudBlazor.UnitTests/Components/SelectTests.cs
+++ b/src/MudBlazor.UnitTests/Components/SelectTests.cs
@@ -307,6 +307,77 @@ await comp.WaitForAssertionAsync(() =>
             });
         }
 
+        [Test]
+        public async Task MultiSelect_ChildlessEnumItems_ShouldUpdateCheckboxImmediately()
+        {
+            const string uncheckedIcon =
+                "M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z";
+            const string checkedIcon =
+                "M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z";
+
+            var comp = Context.Render<MultiSelectChildlessEnumToStringFuncTest>();
+            var select = comp.FindComponent<MudSelect<MultiSelectChildlessEnumToStringFuncTest.Pizza>>();
+
+            await comp.Find("div.mud-input-control").MouseDownAsync();
+            await comp.WaitForAssertionAsync(() => comp.FindAll("div.mud-list-item").Count.Should().Be(4));
+
+            IReadOnlyList<IElement> Items() => comp.FindAll("div.mud-list-item");
+
+            await comp.WaitForAssertionAsync(() => GetCheckboxPath(Items()[0]).Should().Be(uncheckedIcon));
+
+            await Items()[1].ClickAsync();
+
+            await comp.WaitForAssertionAsync(() => select.Instance.ReadText.Should().Be("Diavolo"));
+            await comp.WaitForAssertionAsync(() => GetCheckboxPath(Items()[1]).Should().Be(checkedIcon));
+            await comp.WaitForAssertionAsync(() => GetCheckboxPath(Items()[0]).Should().Be(uncheckedIcon));
+        }
+
+        [Test]
+        public async Task MultiSelect_ChildlessStringItems_ShouldUpdateCheckboxImmediately()
+        {
+            const string uncheckedIcon =
+                "M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z";
+            const string checkedIcon =
+                "M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z";
+
+            var comp = Context.Render<MultiSelectChildlessStringTest>();
+            var select = comp.FindComponent<MudSelect<string>>();
+
+            await comp.Find("div.mud-input-control").MouseDownAsync();
+            await comp.WaitForAssertionAsync(() => comp.FindAll("div.mud-list-item").Count.Should().Be(4));
+
+            IReadOnlyList<IElement> Items() => comp.FindAll("div.mud-list-item");
+
+            await comp.WaitForAssertionAsync(() => GetCheckboxPath(Items()[2]).Should().Be(uncheckedIcon));
+
+            await Items()[2].ClickAsync();
+
+            await comp.WaitForAssertionAsync(() => select.Instance.ReadText.Should().Be("Margarita"));
+            await comp.WaitForAssertionAsync(() => GetCheckboxPath(Items()[2]).Should().Be(checkedIcon));
+            await comp.WaitForAssertionAsync(() => GetCheckboxPath(Items()[0]).Should().Be(uncheckedIcon));
+        }
+
+        [Test]
+        public async Task MultiSelect_SelectAll_ShouldUpdateChildlessItemCheckboxesImmediately()
+        {
+            const string checkedIcon =
+                "M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z";
+
+            var comp = Context.Render<MultiSelectChildlessSelectAllTest>();
+            var select = comp.FindComponent<MudSelect<string>>();
+
+            await comp.Find("div.mud-input-control").MouseDownAsync();
+            await comp.WaitForAssertionAsync(() => comp.FindAll("div.mud-list-item").Count.Should().Be(5));
+
+            IReadOnlyList<IElement> Items() => comp.FindAll("div.mud-list-item");
+
+            await Items()[0].ClickAsync();
+
+            await comp.WaitForAssertionAsync(() => select.Instance.GetState(x => x.SelectedValues).Should().HaveCount(4));
+            await comp.WaitForAssertionAsync(() => select.Instance.ReadText.Should().Be("Cardinale, Diavolo, Margarita, Spinaci"));
+            await comp.WaitForAssertionAsync(() => Items().Skip(1).Should().AllSatisfy(item => GetCheckboxPath(item).Should().Be(checkedIcon)));
+        }
+
         /// <summary>
         /// Initially we have a value of 17 which is not in the list. So we render it as text via MudInput
         /// </summary>
@@ -1982,5 +2053,10 @@ await selectComponent.SetParametersAndRenderAsync(parameters => parameters
             filler.TextContent.Should().Contain("EXTREMELY LONG ITEM 1");
             filler.InnerHtml.Should().NotContain("custom-render");
         }
+
+        private static string GetCheckboxPath(IElement item)
+        {
+            return item.QuerySelectorAll("path").Last().GetAttribute("d")!;
+        }
     }
 }