#12666·MudBlazor

v9.0.0 Migration Guide

Author: ScarletKuroCreated Feb 11, 2026Updated Sep 14, 2026
Labelsdocs

Previous migration guide: v7.0.0 Migration Guide, v8.0.0 Migration Guide.

MudBlazor version 9.0.0 brings significant *breaking changes. This migration guide will help you upgrade from v8 to v9.

Note: Please limit discussion strictly to migration or reporting errors in this guide. For general feedback about version 9.0.0, use the appropriate discussion channels.

[!WARNING] Many obsolete APIs marked for removal in v8 have been removed in v9. The compiler will NOT always catch these changes at compile time if you're using dynamic invocation or reflection. Ensure thorough testing after migration.

Converters: Complete Rework

The converter system has been completely redesigned for better performance, type safety, and extensibility. This is one of the most significant breaking changes in v9.

Removed:

  • Converter<T, U> class
  • Converter<T> class
  • DefaultConverter (old implementation)
  • BoolConverter (old implementation)
  • DateConverter
  • NumericConverter.AreEqual method
  • Converters static class

Replaced with:

  • IConverter<TInput, TOutput> interface
  • ICultureAwareConverter<TInput, TOutput> interface
  • IReversibleConverter<TInput, TOutput> interface
  • DefaultConverter<T> (new implementation)
  • BoolConverter<T> (new implementation)
  • RangeConverter<T>
  • DeferredConverter<TInput, TOutput>
  • EmptyConverter<TInput, TOutput>
  • ConversionResult<T> for error handling
  • ConverterExtensions for fluent API
  • Conversions static class for common conversions

Breaking Changes:

  1. Custom converters must implement interfaces:

Before (v8):

csharp
public class MyConverter : Converter<MyType>
{
    public MyConverter()
    {
        SetFunc = value => value?.ToString() ?? string.Empty;
        GetFunc = str => MyType.Parse(str);
    }
}

After (v9):

csharp
public class MyConverter : IReversibleConverter<MyType, string>
{
    public string Convert(MyType input)
    {
        // ...
    }

    public MyType ConvertBack(string input)
    {
        // ...
    }
}
  1. Inline converters

Before (v8):

csharp
private Converter<ConverterElement?> _elementConverter = new Converter<ConverterElement?>
{
	SetFunc = value => value?.ToString(),
	GetFunc = text => new ConverterElement { Name = text }
};

After (v9):

csharp
private IConverter<ConverterElement?, string?> _elementConverter = Conversions
	.From((ConverterElement? value) => value?.ToString(),
		text => new ConverterElement { Name = text });
  1. Component Converter property changes:

Components like MudTextField<T>, MudNumericField<T>, etc. still have a Converter property, but it now expects the new converter types. The framework provides automatic conversion for most built-in types.

  1. GetDefaultConverter() method added (PR #12365):

All components inheriting from MudFormComponent must now implement a GetDefaultConverter() method instead of setting the converter in the constructor. This provides compile-time safety and allows wrapper components to use null for the Converter parameter.

Before (v8):

csharp
public class MyInput : MudFormComponent<MyType, string>
{
    public MyInput()
    {
        Converter = new DefaultConverter<MyType>
        {
            Culture = GetCulture,
            Format = GetFormat
        };
    }
}

After (v9):

csharp
public class MyInput : MudFormComponent<MyType, string>
{
    // Constructor no longer sets Converter

    protected override IConverter<MyType?, string?> GetDefaultConverter()
    {
        return new DefaultConverter<MyType>
        {
            Culture = GetCulture,
            Format = GetFormat
        };
    }
}

Key changes:

  • Converter parameter is now nullable: You can set Converter to null, and the component will use GetDefaultConverter() as fallback
  • Compile-time safety: Forgetting to implement GetDefaultConverter() now causes a compile error instead of a runtime exception
  • Better for wrappers: Wrapper components no longer need to explicitly pass a converter if they want default behavior

Important: The Converter parameter is checked first. If it's null, GetDefaultConverter() is called once and cached. Use GetConverter() method (not the Converter property) when you need to access the active converter in component logic.

Example accessing the converter:

csharp
// ❌ Don't access Converter directly if it might be null
var converter = Converter; // May be null!

// ✅ Use GetConverter() which handles the fallback
var converter = GetConverter(); // Always returns non-null

Migration tip: If you created custom converters, you'll need to rewrite them to implement the new interfaces. See PR #12177 for detailed examples. If you inherit from MudFormComponent, implement GetDefaultConverter() (PR #12365).

Remove All Obsolete/Deprecated Code

All code marked with [Obsolete] or [Deprecated] in v8 has been removed in v9.

Removed from DialogService:

  • Show(Type) - use ShowAsync(Type)
  • Show<T>() - use ShowAsync<T>()
  • ShowMessageBox() - use ShowMessageBoxAsync()
  • ShowForm<T>() - use ShowFormAsync<T>()
  • Close() - use CloseAsync()

Removed from MudDataGrid:

  • ExpandAllGroups() - use ExpandAllGroupsAsync
  • CollapseAllGroups() - use CollapseAllGroupsAsync

Removed from MudSelect:

  • Clear - use ClearAsync

Removed from MudTabs:

  • ActivatePanel - use ActivatePanelAsync

Removed from MudMenu:

  • Stylename

Removed from ElementReferenceExtensions:

  • MudDetachBlurEventWithJS - use Use mudElementRef.removeOnBlurEvent via js invoke instead

More details: #12142

MudGlobal: Theming Properties Removed

All theming-related properties have been removed from MudGlobal. These experimental properties created maintenance burden and blurred the boundary between behavioral and visual concerns. Use CSS variables, theme configuration, or explicit component parameters instead.

Removed from MudGlobal:

  • MudGlobal.Rounded (static property)
  • MudGlobal.ButtonDefaults.Color (default: Color.Default)
  • MudGlobal.ButtonDefaults.Variant (default: Variant.Text)
  • MudGlobal.InputDefaults.ShrinkLabel (default: false)
  • MudGlobal.InputDefaults.Variant (default: Variant.Text)
  • MudGlobal.InputDefaults.Margin (default: Margin.None)
  • MudGlobal.LinkDefaults.Color (default: Color.Primary)
  • MudGlobal.LinkDefaults.Typo (default: Typo.body1)
  • MudGlobal.LinkDefaults.Underline (default: Underline.Hover)
  • MudGlobal.GridDefaults.Spacing (default: 6)
  • MudGlobal.StackDefaults.Spacing (default: 3)
  • MudGlobal.PopoverDefaults.Elevation (default: 8)

Retained non-theming properties in MudGlobal:

  • DialogDefaults.DefaultFocus
  • MenuDefaults.HoverDelay
  • PopoverDefaults.ModalOverlay
  • TooltipDefaults.Delay/Duration
  • TransitionDefaults.Delay/Duration
  • UnhandledExceptionHandler

Components affected: All affected components now use hard-coded defaults matching the previous MudGlobal default values:

  • MudButton, MudIconButton, MudToggleIconButton - now default to Color.Default and Variant.Text
  • MudBaseInput and all derived inputs (MudTextField, MudNumericField, etc.) - now default to Variant.Text, Margin.None, and ShrinkLabel = false
  • MudLink - now defaults to Color.Primary, Typo.body1, and Underline.Hover
  • MudGrid - now defaults to Spacing = 6
  • MudStack - now defaults to Spacing = 3
  • MudPopover - now defaults to Elevation = 8
  • MudPicker and all derived pickers - now default to Elevation = 8 for the popover
  • Components with Square/Rounded parameters (MudAlert, MudAvatar, MudAvatarGroup, MudCard, MudDataGrid, MudExpansionPanels, MudNavMenu, MudPaper, MudPicker, MudPopover, MudProgressCircular, MudProgressLinear, MudSimpleTable, MudTable, MudTabs) - no longer respect MudGlobal.Rounded

Migration: Users relying on global theming should migrate to:

  1. Explicit component parameters - Set properties directly on each component
  2. Theme tokens - Use theme configuration for colors, typography, and shape
  3. Wrapper components - Create app-specific wrapper components for shared styling
  4. CSS - Apply custom styles via CSS classes or variables

Example migration:

Before (v8):

csharp
// Program.cs or Startup.cs
MudGlobal.ButtonDefaults.Variant = Variant.Filled;
MudGlobal.InputDefaults.Variant = Variant.Outlined;

After (v9) - Option 1: Explicit parameters:

xml
<MudButton Variant="Variant.Filled">Click Me</MudButton>
<MudTextField Variant="Variant.Outlined" />

After (v9) - Option 2: Wrapper component:

razor
@* AppButton.razor *@
<MudButton Variant="Variant.Filled" Class="@Class" @attributes="AdditionalAttributes">
    @ChildContent
</MudButton>

@code {
    [Parameter] public string? Class { get; set; }
    [Parameter] public RenderFragment? ChildContent { get; set; }
    [Parameter(CaptureUnmatchedValues = true)] 
    public IDictionary<string, object>? AdditionalAttributes { get; set; }
}

More details: #12141

DialogService

ShowMessageBox Renamed

Replace ShowMessageBox with ShowMessageBoxAsync:

More details: #12292

Dialog.DefaultFocus Moved

MudGlobal.DialogDefaults.DefaultFocus has been moved to MudDialogProvider.

Before (v8):

csharp
MudGlobal.DialogDefaults.DefaultFocus = DefaultFocus.FirstChild;

After (v9):

xml
<MudDialogProvider DefaultFocus="DefaultFocus.FirstChild" />

Or set it via DialogOptions:

csharp
var options = new DialogOptions { DefaultFocus = DefaultFocus.FirstChild };

More details: #12297

MudTheme: Palette Type Changes

PaletteLight and PaletteDark are now of type Palette instead of their specific types.

Before (v8):

csharp
PaletteLight PaletteLight { get; set; }
PaletteDark PaletteDark { get; set; }

After (v9):

csharp
Palette PaletteLight { get; set; }
Palette PaletteDark { get; set; }

This should have minimal impact as both derive from Palette.

More details: #12148

Transition Defaults Moved

Popover transition defaults moved from MudGlobal to PopoverOptions.

Before (v8):

csharp
MudGlobal.PopoverDefaults.TransitionDuration = 300;

After (v9):

csharp
builder.Services.AddMudServices(config =>
{
    config.PopoverOptions.TransitionDuration = 300;
});

More details: #12300

MudMenu: MenuContext Replaces IActivatable

MudMenu.ActivatorContent now receives a MenuContext parameter instead of using IActivatable via cascading value. The MenuContext provides explicit async methods (OpenAsync, CloseAsync, ToggleAsync, CloseAllAsync) for controlling menus.

Breaking Changes:

  1. ActivatorContent signature changed from RenderFragment? to RenderFragment<MenuContext>?
  2. Menu is no longer opened implicitly - You must explicitly call context methods in event handlers
  3. IActivatable.Activate method removed from MudMenu
  4. Root div event handlers only fire for default activators (Button, Icon, Label)

MenuContext API:

csharp
public sealed class MenuContext
{
    public Task OpenAsync(EventArgs? args = null);
    public Task CloseAsync();
    public Task ToggleAsync(EventArgs? args = null);
    public Task CloseAllAsync();
}

Migration Examples:

Before (v8) - Implicit activation:

razor
<MudMenu>
    <ActivatorContent>
        <MudButton Variant="Variant.Filled">Open Menu</MudButton>
    </ActivatorContent>
    <ChildContent>
        <MudMenuItem>Item 1</MudMenuItem>
    </ChildContent>
</MudMenu>

After (v9) - Explicit context usage:

razor
<MudMenu>
    <ActivatorContent>
        <MudButton Variant="Variant.Filled" OnClick="@context.ToggleAsync">Open Menu</MudButton>
    </ActivatorContent>
    <ChildContent>
        <MudMenuItem>Item 1</MudMenuItem>
    </ChildContent>
</MudMenu>

Left Click:

razor
<MudMenu ActivationEvent="MouseEvent.LeftClick">
    <ActivatorContent>
        <MudChip OnClick="@(() => context.ToggleAsync())">Click Me</MudChip>
    </ActivatorContent>
</MudMenu>

Right Click:

razor
<MudMenu ActivationEvent="MouseEvent.RightClick">
    <ActivatorContent>
        <div @oncontextmenu="@context.ToggleAsync" @oncontextmenu:preventDefault="true">
            <MudChip>Right Click Me</MudChip>
        </div>
    </ActivatorContent>
</MudMenu>

Mouse Over:

razor
<MudMenu ActivationEvent="MouseEvent.MouseOver">
    <ActivatorContent>
        <div @onpointerenter="@context.OpenAsync" @onpointerleave="@context.CloseAsync">
            <MudChip>Hover Over Me</MudChip>
        </div>
    </ActivatorContent>
</MudMenu>

Positioned at Cursor:

razor
<MudMenu PositionAtCursor="true">
    <ActivatorContent>
        @* Pass event args to OpenAsync/ToggleAsync for cursor positioning *@
        <div @onclick="@context.ToggleAsync" style="cursor: pointer">
            <MudCard>
                <MudCardContent>Click anywhere on this card</MudCardContent>
            </MudCard>
        </div>
    </ActivatorContent>
</MudMenu>

Non-interactive Activators:

For non-interactive elements like MudAvatar, wrap them in a div with event handlers:

razor
<MudMenu>
    <ActivatorContent>
        <div @onclick="@context.ToggleAsync" style="cursor: pointer">
            <MudAvatar>
                <MudImage Src="avatar.jpg" />
            </MudAvatar>
        </div>
    </ActivatorContent>
</MudMenu>

More details: #12145

MudFormComponent & MudBaseInput: API Changes

The API had inconsistent naming between MudFormComponent and MudBaseInput that has been fixed for consistency.

MudFormComponent: Error and ErrorId Two-Way Bindable

Error and ErrorId are now two-way bindable parameters.

New capability (v9):

xml
<MudTextField @bind-Error="myError" @bind-ErrorId="myErrorId" />

More details: #12138, #12140

Method Naming Changes

Several methods have been renamed for consistency:

  • Reset()ResetAsync() (was already marked async)
  • Validate()ValidateAsync() (if async)
  • ReadValue()ReadValue (property-style, no parentheses)

More details: #12147, #12310

WriteValueAsync and SetValueAsync Renamed

Breaking Changes (PR #12312):

  1. MudFormComponent.WriteValueAsync renamed to SetValueAsync then to SetValueCoreAsync:

To match the ReadValue property pattern (Read/Set), WriteValueAsync was renamed, then later renamed again with Core suffix.

Before (v8):

csharp
protected internal virtual T? ReadValue { get; }
protected virtual Task WriteValueAsync(T? value)

After (v9 - Final):

csharp
protected internal virtual T? ReadValue { get; }
protected virtual Task SetValueCoreAsync(T? value)  // was WriteValueAsync → SetValueAsync → SetValueCoreAsync
  1. MudBaseInput.SetValueAsync(T?, bool, bool) renamed to SetValueAndUpdateTextAsync:

To mirror the existing SetTextAndUpdateValueAsync method and avoid conflict with the base SetValueAsync method.

  1. SetTextAsync renamed to SetTextCoreAsync:

For consistency with the Core suffix pattern.

Before (v8):

csharp
// Text API
protected internal string? ReadText { get; }
protected Task SetTextAsync(string? text);
protected Task SetTextAndUpdateValueAsync(string? text, bool updateValue = true);

// Value API (inconsistent)
protected internal T? ReadValue { get; }
protected Task SetValueAsync(T? value, bool updateText = true, bool force = false);

After (v9 - Final):

csharp
// Text API
protected internal string? ReadText { get; }
protected Task SetTextCoreAsync(string? text);  // was SetTextAsync
protected Task SetTextAndUpdateValueAsync(string? text, bool updateValue = true);

// Value API
protected internal T? ReadValue { get; }
protected virtual Task SetValueCoreAsync(T? value);  // was WriteValueAsync → SetValueAsync
protected Task SetValueAndUpdateTextAsync(T? value, bool updateText = true, bool force = false);  // was SetValueAsync(T?, bool, bool)

Migration:

If you override WriteValueAsync in a custom component:

Before (v8):

csharp
protected override Task WriteValueAsync(MyType? value)
{
    _value = value;
    return Task.CompletedTask;
}

After (v9):

csharp
protected override Task SetValueCoreAsync(MyType? value)
{
    _value = value;
    return Task.CompletedTask;
}

If you call SetValueAsync(value, updateText, force) in a custom input:

Before (v8):

csharp
await SetValueAsync(newValue, updateText: true, force: false);

After (v9):

csharp
await SetValueAndUpdateTextAsync(newValue, updateText: true, force: false);

If you were calling SetTextAsync internally:

Before (v8):

csharp
await SetTextAsync(newText);

After (v9):

csharp
await SetTextCoreAsync(newText);

All are protected APIs, so external impact should be minimal. Only affects custom components that inherit from MudFormComponent or MudBaseInput and override these methods.

More details: #12312, #12489

MudSelect

Comparer

Now requires a proper GetHashCode implementation that doesn’t throw an exception. If you are using EqualityComparer<T>.Create(equals, getHashCode), you must provide the getHashCode parameter, otherwise, a runtime exception will occur.

SelectedValues Changed to IReadOnlyCollection

Before (v8):

csharp
ICollection<T> SelectedValues { get; set; }

After (v9):

csharp
IReadOnlyCollection<T> SelectedValues { get; set; }

More details: #12619

EventListener / EventManager Removed

The EventListener, EventListenerFactory, and related event management infrastructure have been completely removed.

Removed:

  • IEventListener / EventListener
  • IEventListenerFactory / EventListenerFactory
  • IEventManager
  • WebEventJsonContext

More details: #12532

Range and DateRange: Setters Removed

Range<T>.Start, Range<T>.End, DateRange.Start, and DateRange.End properties no longer have setters. These classes should now be treated as immutable to ensure proper GetHashCode() behavior and thread safety.

Breaking Changes:

  1. Range<T> properties are now read-only (init-only)
  2. DateRange properties are now read-only (init-only)
  3. Must create new instances instead of mutating existing ones

Complete API Changes:

Before (v8):

csharp
public class Range<T>
{
    public T? Start { get; set; }  // Had setter
    public T? End { get; set; }    // Had setter
}

public class DateRange : Range<DateTime?>
{
    // Inherited mutable properties
}

After (v9):

csharp
public class