MudBlazor
Apparence
Links
Layouts
Components/Layout/MainLayout.razor |
@inherits LayoutComponentBase <MudThemeProvider IsDarkMode="true" /> <MudPopoverProvider /> <MudDialogProvider /> <MudSnackbarProvider /> <div> @*<MudAppBar> My Application </MudAppBar>*@ <MudLayout> <MudDrawer Open="true"> <MudDrawerHeader Class="cursor-help mb-1 mx-auto"> <MudTooltip Text="@($"Version {AssemblyVersion}")" Placement="Placement.Right" Arrow="true" Duration="1000"> <MudText Typo="Typo.h4" Color="Color.Primary">My Application</MudText> </MudTooltip> </MudDrawerHeader> <NavMenu /> </MudDrawer> <MudMainContent Class="py-2"> <MudContainer MaxWidth="MaxWidth.Large"> @Body </MudContainer> </MudMainContent> </MudLayout> </div> @code { private string AssemblyVersion => typeof(MainLayout).Assembly.GetName().Version?.ToString() ?? "0.0.0"; } |
Components/Layout/MainLayout.razor.css |
::deep .mud-drawer-content { background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); } |
Components/Layout/NavMenu.razor |
<div> <MudNavMenu> <MudNavLink Href="" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Home" IconColor="Color.Primary"> Home </MudNavLink> <MudNavLink Href="menu1" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Diversity1" IconColor="Color.Secondary"> Menu 1 </MudNavLink> </MudNavMenu> </div> |
Components/Layout/NavMenu.razor.css |
::deep .mud-nav-item { font-size: 0.9rem; padding: 0 1rem 0.5rem; } ::deep .mud-nav-item a { height: 3rem; border-radius: 4px; display: flex; align-items: center; } ::deep .mud-nav-link:hover:not(.mud-nav-link-disabled) { background-color: rgba(255,255,255,0.1); /* var(--mud-palette-action-default-hover); */ } ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled) { background-color: rgba(255,255,255,0.1); color: inherit; } ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):hover:not(.mud-nav-link-disabled), ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):focus-visible:not(.mud-nav-link-disabled) { background-color: rgba(255,255,255,0.1); } |
Components
Button
<MudButton Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.Delete" Color="Color.Error">Delete</MudButton> <MudButton Href="https://github.com/MudBlazor/MudBlazor" Target="_blank" Variant="Variant.Filled" EndIcon="@Icons.Custom.Brands.GitHub" Color="Color.Primary"> Link </MudButton> <MudButton Disabled="@processing" OnClick="Process" Variant="Variant.Filled" Color="Color.Primary"> @if (processing) { <MudProgressCircular Class="ms-n1" Size="Size.Small" Indeterminate="true" /> <MudText Class="ms-2">Processing</MudText> } else { <MudText>Click me</MudText> } </MudButton> @code { private bool processing = false; async Task ProcessAsync() { processing = true; await Task.Delay(2000); processing = false; } } |
Dialog
Pages/MyPage.razor.cs |
[Inject] private IDialogService DialogService { get; set; } private void OnDisplayDialog() { var dialogOptions = new DialogOptions() { CloseButton = true }; var parameters = new DialogParameters<MyDialog> { { x => x.MyProperty, propertyValue } }; DialogService.Show<MyDialog>("Title", parameters, dialogOptions); |
Dialogs/MyDialog.razor |
<MudDialog> <DialogContent> </DialogContent> <DialogActions> <MudButton OnClick="Cancel">Cancel</MudButton> <MudButton Color="Color.Success" Variant="Variant.Filled" OnClick="Submit">Ok</MudButton> </DialogActions> </MudDialog> @code { [Parameter] MyDto MyProperty { get; set; } [CascadingParameter] MudDialogInstance MudDialog { get; set; } void Submit() => MudDialog.Close(DialogResult.Ok(true)); void Cancel() => MudDialog.Cancel(); } |
IconButton
<MudIconButton Icon="@Icons.Material.Filled.DeleteForever" Size="Size.Small" Color="Color.Error" aria-label="delete" OnClick="@(() => OnDeleteAsync(@context.Id))" Href="@($"items/{context.Id}")" /> |
Image
<MudImage Src="images/MyImage.svg" Alt="MyImage" Width="200" Elevation="10" Class="rounded-lg" /> |
Link
<MudLink Href="" Typo="Typo.h3" Underline="Underline.None" Color="Color.Default" Class="purple-text text-lighten-3">Text</MudLink> |
List
<MudPaper MaxHeight="200px" Class="mt-1 overflow-y-auto border-solid border mud-border-error"> <MudList T="string" Dense="true" Class="mud-error-text"> @foreach (var error in Errors) { <MudListItem Text="@error" Icon="@Icons.Material.Filled.ErrorOutline" IconColor="Color.Error" /> } </MudList> </MudPaper> @code { [Parameter] public IReadOnlyCollection<string> Errors { get; set; } = Array.Empty<string>(); } |
Menu
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Size="Size.Small" AnchorOrigin="Origin.CenterLeft" TransformOrigin="Origin.CenterRight"> <MudMenuItem> <MudIconButton Icon="@Icons.Material.Filled.DeleteForever" Size="Size.Small" Color="Color.Error" aria-label="delete" OnClick="@(() => OnDeleteAsync(@context.Id))" /> </MudMenuItem> </MudMenu> |
Expansion Panels
Pages/MyPage.razor |
<MudExpansionPanels MultiExpansion="true"> <MudExpansionPanel Text="Panel One"> Panel One Content </MudExpansionPanel> <MudExpansionPanel Text="Panel Two"> Panel Two Content </MudExpansionPanel> </MudExpansionPanels> |
MessageBox
Pages/MyPage.razor |
<MudButton Variant="Variant.Filled" Color="Color.Error" OnClick="OnDeleteAsync">Delete</MudButton> <MudMessageBox @ref="deleteItemDialog" Title="Item deletion"> <MessageContent> Are you sure you want to delete the selected item? </MessageContent> <YesButton> <MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.DeleteForever">Delete</MudButton> </YesButton> </MudMessageBox> |
Pages/MyPage.razor.cs |
private async Task OnDeleteAsync() { var dialogOptions = new DialogOptions { CloseButton = true, DisableBackdropClick = true // modal }; var result = await deleteItemDialog.ShowAsync(dialogOptions); if (result != true) { return; } |
<MudNavMenu> <MudNavLink Href="" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Home" IconColor="Color.Primary"> Home </MudNavLink> <MudNavLink Href="menu1" Icon="@Icons.Material.Filled.Diversity1" IconColor="Color.Secondary"> Menu 1 </MudNavLink> </MudNavMenu> |
Progress
<MudProgressCircular Color="Color.Primary" Size="Size.Small" Indeterminate="true" /> <MudProgressCircular Color="Color.Primary" Size="Size.Small" Value="@progressValue" /> |
private double progressValue; // between 0 and 100 |
SnackBar
Pages/MyPage.razor.cs |
[Inject] private ISnackbar snackbar { get; set; } = default!; snackbar.Add("Success!!!", Severity.Success); snackbar.Add(new MarkupString($"Error!!!<br>Error message:<br>{operationStatus.ErrorMessage}"), Severity.Error); |
Program.cs |
builder.Services.AddMudServices(config => { config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomRight; config.SnackbarConfiguration.PreventDuplicates = true; config.SnackbarConfiguration.HideTransitionDuration = 100; }); |
Text
<MudText Typo="Typo.h6" Align="Align.Center"> Title </MudText> |
Table
![]() |
No validation in inline editing mode |
<MudTable Items="@jobs" Dense="true" Hover="true" Bordered="true" Striped="true"> <HeaderContent> <MudTh>Description</MudTh> <MudTh></MudTh> </HeaderContent> <RowTemplate> <MudTd DataLabel="Description">@context.Description</MudTd> <MudTd DataLabel="Actions"> <MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Small" Color="Color.Primary" aria-label="edit" Href="@($"jobs/{context.Id}")" /> <MudIconButton Icon="@Icons.Material.Filled.DeleteForever" Size="Size.Small" Color="Color.Error" aria-label="delete" OnClick="@(() => OnDeleteAsync(@context.Id))"/> </MudTd> </RowTemplate> </MudTable> @code { private List<Job>? jobs; private async Task OnDeleteAsync(int id) { var jobToDelete = jobs.FirstOrDefault(x => x.Id == id) if (jobToDelete != null) { jobs!.Remove(jobToDelete); await InvokeAsync(StateHasChanged); } } } |
Multi-Selection
<MudTable Items="@jobs" MultiSelection="true" @bind-SelectedItems="selectedJobs"> <ToolBarContent> <MudIconButton Icon="@Icons.Material.Filled.Add" Size="Size.Medium" Color="Color.Success" OnClick="@(() => OnAdd())"/> <MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Medium" Color="Color.Primary" Disabled="@(!IsEditActionAvailable)" OnClick="@(() => OnEdit())" /> <MudIconButton Icon="@Icons.Material.Filled.DeleteForever" Size="Size.Medium" Color="Color.Error" Disabled="@(!IsDeleteActionAvailable)" OnClick="@(() => OnDelete())"/> </ToolBarContent> <HeaderContent> <MudTh>Description</MudTh> </HeaderContent> <RowTemplate> <MudTd DataLabel="Description">@context.Description</MudTd> </RowTemplate> </MudTable> @code { private List<Job>? jobs; private HashSet<Job> selectedJobs = []; private bool IsEditActionAvailable => selectedJobs.Count == 1; private bool IsDeleteActionAvailable => selectedJobs.Count > 0; private void OnAdd() { var nextId = jobs!.Select(x => x.Id).DefaultIfEmpty(0).Max() + 1; jobs!.Add(new Job { Id = nextId, Description = $"Job {nextId}" }); } private void OnEdit() { } private void OnDelete() { if (IsDeleteActionAvailable) jobs!.RemoveAll(selectedJobs.Contains); } } |
Sorting
<MudTable Items="@items" SortLabel="Sort By" AllowUnsorted="false"> <HeaderContent> <MudTh>Name</MudTh> <!-- no sorting on this column --> <MudTh> <MudTableSortLabel SortBy="new Func<Item, object>(x => x.Price)" InitialDirection="SortDirection.Descending"> <!-- sorting by default on this column --> Price </MudTableSortLabel> </MudTh> </HeaderContent> |
Server Side Filtering, Sorting and Pagination
<MudTable @ref="table" ServerData="ServerReloadAsync" Hover="true" Loading="@isSearching" LoadingProgressColor="Color.Info" Class="flex-column"> <ToolBarContent> <MudFab Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add" Size="Size.Small" Href="item/new" /> <MudTextField T="string" ValueChanged="@(s => OnSearch(s))" Placeholder="Search" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0 mx-16"> </MudTextField> </ToolBarContent> <HeaderContent> <MudTh Style="text-align:right">Name</MudTh> <MudTh></MudTh> </HeaderContent> <RowTemplate> <MudTd DataLabel="Name" Style="text-align:right">@context.Name</MudTd> <MudTd> <MudMenu Icon="@Icons.Material.Filled.MoreVert" Size="Size.Small" AnchorOrigin="Origin.CenterLeft" TransformOrigin="Origin.CenterRight"> <MudMenuItem> <MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Small" Color="Color.Primary" aria-label="edit" Href="@($"transaction/{context.Id}")" /> </MudMenuItem> <MudMenuItem> <MudIconButton Icon="@Icons.Material.Filled.DeleteForever" Size="Size.Small" Color="Color.Error" aria-label="delete" OnClick="@(() => OnDeleteAsync(context.Id))" /> </MudMenuItem> </MudMenu> </MudTd> </RowTemplate> <NoRecordsContent> <MudText>No matching items found</MudText> </NoRecordsContent> <LoadingContent> <MudText>Loading...</MudText> </LoadingContent> <PagerContent> <MudTablePager PageSizeOptions="new int[] { 10 }" HorizontalAlignment="HorizontalAlignment.Left" HideRowsPerPage="true" /> </PagerContent> </MudTable> |
private bool isSearching; private async Task<TableData<ItemResponse>> ServerReloadAsync(TableState state, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(state); errors.Clear(); isSearching = true; var query = itemQueryBuildService.BuildQuery(searchText, state.Page + 1, state.PageSize); var result = await itemClient.GetPagedAsync(query, CancellationToken.None); TableData<ItemResponse> data; if (!result.IsValid) { errors.AddRange(result.ErrorMessages); data = new TableData<ItemResponse> { Items = Enumerable.Empty<ItemResponse>() }; } else { data = new TableData<ItemResponse> { TotalItems = result.Result.TotalCount, Items = result.Result.Response }; } isSearching = false; return data; } private void OnSearch(string searchText) { this.searchText = searchText; table.ReloadServerData(); } private async Task OnDeleteAsync(uint ItemId) { var dialogOptions = new DialogOptions { CloseButton = true }; var result = await deleteItemDialog.Show(dialogOptions); if (result != true) { return; } var operationStatus = await itemClient.DeleteItemAsync(itemId, CancellationToken.None); if (operationStatus.Success) { snackbar.Add("The item has been well deleted", Severity.Success); } else { snackbar.Add( $"The item has not been deleted.<br>Error message:<br>{operationStatus.ErrorMessage}", Severity.Error); } } |
Inline Edit Mode
<MudTable @ref="itemsTable" RowEditPreview="BackupElement" RowEditCancel="ResetElementToOriginalValues" RowEditCommit="SaveChangesAsync" EditTrigger="TableEditTrigger.EditButton" CanCancelEdit="true"> <ColGroup> <col style="width:120px;" /> <col style="width:100px;" /> @* force the size of the button column *@ </ColGroup> <RowEditingTemplate> <MudTd DataLabel="Name"> <MudTextField @bind-Value="context.Name" Required /> </MudTd> <MudTd DataLabel="Price"> <MudNumericField @bind-Value="context.Price" HideSpinButtons="true" Required /> </MudTd> <MudTd DataLabel="Date"> <MudDatePicker @bind-Date="context.Date" Required /> </MudTd> <MudTd DataLabel="Category"> <MudSelect T="Namespace.Category" @bind-Value="@context.Category"> @foreach (var category in categories) { <MudSelectItem Value="@category">@category.Code</MudSelectItem> } </MudSelect> </MudTd> </RowEditingTemplate> <EditButtonContent Context="button"> <MudIconButton Size="@Size.Small" Icon="@Icons.Material.Outlined.Edit" Class="pa-0" OnClick="@button.ButtonAction" Disabled="@button.ButtonDisabled" /> <MudIconButton Size="@Size.Small" Icon="@Icons.Material.Outlined.Delete" Color="Color.Error" Class="pa-0 ml-2" OnClick="@(() => DeleteElementAsync(button.Item))" Disabled="@button.ButtonDisabled" /> </EditButtonContent> </MudTable> |
private MudTable<ItemViewModel>? itemsTable; private ItemViewModel? itemBeforeEdit; private void BackupElement(object element) => itemBeforeEdit = new((ItemViewModel)element); private void ResetElementToOriginalValues(object element) { if (itemBeforeEdit != null && element is ItemViewModel itemViewModel) { itemViewModel.Name = itemBeforeEdit.Name; } } private async void SaveChangesAsync(object element) { var item = (ItemViewModel)element; var query = new CreateUpdateItemQuery(item); var operationStatus = await itemClient .UpdateItemAsync(item.Id, query, CancellationToken.None); // because RowEditCommit is an Action<object> it doesn't return a Task and it doesn't wait the task // if you change the Component you have to call StateHasChanged to update it StateHasChanged(); } private async Task AddElementAsync() { var item = new ItemViewModel(); itemsList.Add(item); await Task.Yield(); // Ensure the UI updates before setting the editing item itemsTable!.SetEditingItem(item); } private async void DeleteElementAsync(object? element) { if (element is not ItemViewModel item) return; var dialogOptions = new DialogOptions { CloseButton = true }; var result = await deleteDialog.ShowAsync(dialogOptions); if (result != true) return; var operationStatus = await ItemClient.DeleteAsync(item.Id, CancellationToken.None); if (operationStatus.Success) { Snackbar.Add("The item has been well deleted", Severity.Success); itemsList.Clear(); var getAllResult = await ExchangeRateClient.GetAllAsync(CancellationToken.None); if (getAllResult.Success && getAllResult.Result is not null) itemsList.AddRange(getAllResult.Result.Select(x => new ItemViewModel(x))); StateHasChanged(); } else Snackbar.Add($"The item has not been deleted.<br>{operationStatus.ErrorMessage}", Severity.Error); } |
Tabs
<MudTabs Elevation="2" Rounded="true" Centered="true" ApplyEffectsToContainer="true" PanelClass="pa-6"> <MudTabPanel Text="Tab text 1">...</MudTabPanel> <MudTabPanel Text="Tab text 2">...</MudTabPanel> </MudTabs> |
Tooltip
<MudTooltip Text="Delete" Placement="Placement.Right" Arrow="true" Duration="1000"> <MudIconButton Icon="@Icons.Material.Filled.Delete" /> </MudTooltip> |
Form
Pages/ItemForm.razor |
@if (Item is not null) { <MudCard> <MudForm @ref="@form" Model="@Item" Validation="@(itemViewModelValidator.ValidateValue)"> <MudCardContent> <MudTextField @bind-Value="Item.Name" For="@(() => Item.Name)" Label="Name" DebounceInterval="500" AutoFocus="true" /> </MudCardContent> </MudForm> <MudCardActions> <MudButton Variant="Variant.Filled" Color="Color.Default" Class="ml-auto mr-2" OnClick="@CancelRequest"> Cancel </MudButton> <MudButton Variant="Variant.Filled" Color="Color.Primary" Class="mr-4" OnClick="@SubmitAsync"> Save </MudButton> </MudCardActions> </MudCard> } |
Pages/ItemForm.razor.cs |
[Parameter] public CreateUpdateItemQuery Item { get; set; } = default!; [Parameter] public EventCallback<bool> ValidationResult { get; set; } [Parameter] public EventCallback CancelRequest { get; set; } private IForm form = default!; private readonly CreateUpdateItemQueryValidator itemQueryValidator = new(); private Task SubmitAsync() => ValidationResult.InvokeAsync(form.IsValid); |
Validators/CreateUpdateItemQueryValidator.cs |
public class CreateUpdateItemQueryValidator : AbstractValidator<CreateUpdateItemQuery> { public CreateUpdateItemQueryValidator() { RuleFor(x => x.Name).NotEmpty(); } public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) => { var result = await ValidateAsync(ValidationContext<CreateUpdateItemQuery>.CreateWithOptions( (CreateUpdateItemQuery)model, x => x.IncludeProperties(propertyName))); return result.IsValid ? (IEnumerable<string>)Array.Empty<string>() : result.Errors.Select(e => e.ErrorMessage); }; } |
Pages/CreateItem.razor |
<ItemForm Item="@item" ValidationResult="@(async (success) => await ValidationResultAsync(success))" CancelRequest="@(() => NavigationManager.NavigateTo("item"))" /> |
Pages/CreateItem.razor.cs |
private async Task ValidationResultAsync(bool isValid) { errors.Clear(); if (!isValid) { return; } var operationStatus = await ItemClient.CreateAsync(item, CancellationToken.None); if (operationStatus.Success) { Snackbar.Add("The item has been well created", Severity.Success); NavigationManager.NavigateTo("item"); } else { // server validation errors errors.AddRange(operationStatus.Errors.SelectMany(x => x.Value)); } } |
![]() |
Add the package FluentValidation to use it. |
Text field
<MudTextField @ref="searchTextField" T="string" ValueChanged="@(s => OnSearch(s))" Placeholder="Search" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Clearable="true" Class="mt-0 mx-16"> |
Radio buttons
<MudField Label="Type" Class="mt-2" Variant="Variant.Text" InnerPadding="false"> <MudRadioGroup @bind-Value="item.Type"> <MudRadio Value="@("type1")" Color="Color.Primary">Type1</MudRadio> <MudRadio Value="@("type2")" Color="Color.Primary">Type2</MudRadio> </MudRadioGroup> </MudField> |
Numeric field
<MudNumericField @bind-Value="item.Quantity" Label="Quantity" Variant="Variant.Text" Min="1" HideSpinButtons="true" /> |
Date picker
<MudDatePicker Label="Date" @bind-Date="item.Date" Culture="@frCulture" /> @code { CultureInfo frCulture = CultureInfo.GetCultureInfo("fr-FR"); } |
Time picker
<MudTimePicker Label="Time" @bind-Time="item.Time" Culture="@frCulture" /> @code { CultureInfo frCulture = CultureInfo.GetCultureInfo("fr-FR"); } |
Autocomplete list
<MudAutocomplete T="MyNamespace.CurrencyResponse" Label="Currency" @bind-Value="item.Currency" SearchFuncWithCancel="@SearchCurrenciesByCodeAsync" ToStringFunc="@(x => x == null ? null : x.Code)" Strict="false" ShowProgressIndicator="true"> <BeforeItemsTemplate> <MudText Class="px-4 py-1">3 characters min.</MudText> </BeforeItemsTemplate> <NoItemsTemplate> <MudText Align="Align.Center" Class="px-4 py-1"> No matching currencies found </MudText> </NoItemsTemplate> |
// call to the web API private async Task<IEnumerable<CurrencyResponse>> SearchCurrenciesByCodeAsync(string value, CancellationToken cancellationToken) => await CurrencyClient.GetCurrenciesByCodeAsync(value, cancellationToken); // cache all the currencies private async Task<IEnumerable<CurrencyResponse>> SearchCurrenciesByCodeAsync(string value) { var currencies = await CurrencyClient.GetAllAsync(); // if text is null or empty, show complete list return string.IsNullOrEmpty(value) ? (IEnumerable<CurrencyResponse>)currencies : currencies.Where(x => x.Code.StartsWith(value, StringComparison.InvariantCultureIgnoreCase)); } |
Select
@* with an enum *@ <MudSelect @bind-Value="logLevel" Label="Log level"> @foreach (LogLevel logLevel in Enum.GetValues(typeof(LogLevel))) { <MudSelectItem Value="@logLevel">@logLevel.ToString()</MudSelectItem> } </MudSelect> @* with a list of items *@ <MudSelect T="Namespace.Item" @bind-Value="@selectedItem" Label="Item" AnchorOrigin="Origin.BottomCenter"> @foreach (var item in items) { <MudSelectItem Value="@item">@item.Description</MudSelectItem> } </MudSelect> @* with static items *@ <MudSelect @bind-Value="@selectedItem" Label="Item"> <MudSelectItem Value="@("Item1")">Item1</MudSelectItem> <MudSelectItem Value="@("Item2")">Item2</MudSelectItem> </MudSelect> |
private readonly List<Item> items = new(); private Item selectedItem = default!; protected override async Task OnInitializedAsync() { // fetch items var item = items.FirstOrDefault(x => x.Description == "best item"); if (item is not null) { selectedItem = item; } StateHasChanged(); } |
File Upload
<MudFileUpload T="IReadOnlyList<IBrowserFile>" Accept=".pdf, .csv" AppendMultipleFiles MaximumFileCount="@(int.MaxValue)" FilesChanged="UploadFileAsync" Class="mt-0"> <ActivatorContent> <MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.UploadFile"> Load </MudButton> </ActivatorContent> </MudFileUpload> |
private async Task UploadFileContentAsync(IReadOnlyList<IBrowserFile> files) { var filesTooBig = files.Where(x => x.Size > 512000).ToArray(); errors.AddRange(filesTooBig.Select(GetFileTooBigErrorMessage)); var tasks = files .Except(filesTooBig) .Select( async x => new { name = x.Name, content = await GetFileContentAsync(x) }); var fileContents = await Task.WhenAll(tasks); static async Task<byte[]> GetFileContentAsync(IBrowserFile file) { using var stream = file.OpenReadStream(); using var memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream); return memoryStream.ToArray(); } } |
Icons
<MudIcon Icon="@Icons.Material.Filled.Sell" Title="Favorite" Color="Color.Primary" Size="Size.Small" /> <MudIconButton Icon="@Icons.Material.Filled.Home" /> <MudIconButton Icon="@myIcon" /> @code { string myIcon = MudBlazor.Icons.Material.Filled.Home; } |
CSS utilities
Margin Padding
Code | Description |
---|---|
m p | margin padding |
t b r l | top bottom right left |
x | left and right |
y | top and bottom |
a | all 4 sides |
0 1 n1 auto | 0 4px -4px auto |
xs sm md lg xl xxl | <600 960 1280 1920 2560 >2560 |
<MudPaper Class="pa-4 mr-16">padding on the 4 sides 16px</MudPaper> <MudPaper Class="mr-16">margin right 64px</MudPaper> <MudPaper Class="ma-1 ma-sm-4 ma-xl-16">margin 4px, 16px on screens from 600-1920px, 64px from 1920px/MudPaper> |
Display
Class | Property |
---|---|
d-block | display: block; |
d-inline | display: inline; |
d-inline-block | display: inline-block; |
d-none | display: none; |
Flexbox
Class | Property |
---|---|
d-flex | display: flex; |
justify-center | justify-content: center; |
justify-space-around | justify-content: space-around; |
justify-end | justify-content: flex-end; |
flex-wrap | flex-wrap: wrap; |
flex-column | flex-direction: column; |
flex-1 | flex: 1 1 0%; |
flex-auto | flex: 1 1 auto; |
flex-initial | flex: 0 1 auto; |
Position
Code | Description |
---|---|
absolute | position: absolute; |
Border
Code | Description |
---|---|
border | border-width: 1px; |
border-solid | border-style: solid; |
mud-border-primary |
Responsivity
Responsivity with Breakpoints
@* hide the MudCard for screens < 600px *@ <MudHidden Breakpoint="Breakpoint.Xs" Invert="true"> <MudCard Class="pa-5"> <MudText>SM and Down</MudText> </MudCard> </MudHidden> |
Responsivity with CSS media queries
::deep .responsive-height { height: 100%; /* Default for smaller screens */ } @media (min-width: 600px) { ::deep .responsive-height { height: 80vh; } } |
Cursor / Mouse
<MudPaper Class="cursor-pointer">Hand with finger raised</MudPaper> <MudPaper Class="cursor-help">Question mark</MudPaper> |
Colors
<MudPaper Color="Color.Primary"> Color enum </MudPaper> <MudPaper Class="mud-info mud-secondary-text"> CSS and theme colors. background: theme info blue; color: theme secondary pink </MudPaper> <!-- mud-theme-primary vs mud-primary --> <MudPaper Style="@($"color:{theme.Palette.Dark}; background:{theme.Palette.Warning};")"> C# and theme colors </MudPaper> <MudPaper Class="blue lighten-4 red-text text-darken-4"> CSS and material colors background: blue lighten4; color: red darken4 </MudPaper> <MudPaper Style="@($"color:{Colors.Blue.Lighten4}; background:{Colors.Red.Darken4};")"> C# and material colors </MudPaper> <span style="color: var(--mud-palette-warning);">C# and CSS colors</span> @code { private MudTheme theme = new MudTheme(); } |
Theme
Components/Layout/MainLayout.razor |
<MudThemeProvider Theme="theme" IsDarkMode="true" /> @code { MudTheme theme = new MudTheme { LayoutProperties = new LayoutProperties { DrawerWidthLeft = "160px" // --mud-drawer-width-left 240px }, PaletteLight = new() { Black = "#110e2d", AppbarText = "#424242", AppbarBackground = "rgba(255,255,255,0.8)", DrawerBackground = "#ffffff", GrayLight = "#e8e8e8", GrayLighter = "#f9f9f9", }, PaletteDark = new() { Primary = "#7e6fff", Surface = "#1e1e2d", Background = "#1a1a27", BackgroundGray = "#151521", AppbarText = "#92929f", AppbarBackground = "rgba(26,26,39,0.8)", DrawerBackground = "#1a1a27", ActionDefault = "#74718e", ActionDisabled = "#9999994d", ActionDisabledBackground = "#605f6d4d", TextPrimary = "#b2b0bf", TextSecondary = "#92929f", TextDisabled = "#ffffff33", DrawerIcon = "#92929f", DrawerText = "#92929f", GrayLight = "#2a2833", GrayLighter = "#1e1e2d", Info = "#4a86ff", Success = "#3dcb6c", Warning = "#ffb545", Error = "#ff3f5f", LinesDefault = "#33323e", TableLines = "#33323e", Divider = "#292838", OverlayLight = "#1e1e2d80", } }; } |
wwwroot/css/theme.css |
/* use theme variables */ .mud-icon-default { color: var(--mud-palette-primary); } /* override theme variables for light and dark theme */ html, body { --mud-palette-action-default-hover: rgba(255, 255, 255, 0.1); } |
Override theme variables for dark mode only
![]() |
Only if the varaible is not available in the PaletteDark |
Components/Layout/MainLayout.razor |
<MudThemeProvider Theme="@theme" IsDarkMode="isDarkMode" /> @* add a dark-mode css class *@ <MudLayout class="@RootCssClass">...</MudLayout> @code { private string RootCssClass => isDarkMode ? "dark-mode" : ""; } |
wwwroot/css/theme.css |
html .dark-mode, body .dark-mode { --mud-palette-action-default-hover: rgba(255, 255, 255, 0.1); } |
CSS isolation
![]() |
|
MyPage.razor |
<div> <MudText Typo="Typo.h1" id="title1">@name</MudText> <h2 id="title2">@name</h2> <div> |
MyPage.razor.css |
::deep title1 { } title2 { } |
Build component in the code-behind
MyPage.razor |
<MudTd DataLabel="Type"> @GetIconFromType(context.Type) </MudTd> |
MyPage.razor.cs |
private static RenderFragment GetIconFromType(string type) => builder => { builder.OpenComponent<MudIcon>(0); builder.AddAttribute(1, "Title", type); builder.AddAttribute(2, "Icon", @Icons.Material.Filled.Money); builder.AddAttribute(3, "Color", type switch { "deposit" => Color.Success, "withdrawal" => Color.Warning, "charge" => Color.Error, _ => throw new NotSupportedException($"The type '{type}' is not supported.") }); builder.CloseComponent(); }; |
Installation
New project from template
# install MudBlazor templates dotnet new install MudBlazor.Templates # help on new project dotnet new mudblazor --help # new server project named MudBlazor dotnet new mudblazor -int Server --no-https --output MudBlazor |
Components/Layout/MainLayout.razor |
<MudThemeProvider IsDarkMode="true" Theme="customTheme" /> @code { MudTheme customTheme = new MudTheme { LayoutProperties = new LayoutProperties { DrawerWidthLeft = "160px" // --mud-drawer-width-left 240px } }; } |
Components/Layout/MainLayout.razor.css |
::deep .mud-drawer-content { background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); } |
Components/Layout/NavMenu.razor.css |
::deep .mud-nav-item { font-size: 0.9rem; padding: 0 1rem 0.5rem; } ::deep .mud-nav-item a { height: 3rem; border-radius: 4px; display: flex; align-items: center; } ::deep .mud-nav-link:hover:not(.mud-nav-link-disabled) { background-color: var(--mud-palette-action-default-hover); /* rgba(255,255,255,0.1); */ } ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled) { background-color: var(--mud-palette-action-default-hover); color: inherit; } ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):hover:not(.mud-nav-link-disabled), ::deep .mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled):focus-visible:not(.mud-nav-link-disabled) { background-color: var(--mud-palette-action-default-hover); } |
Already existing project
dotnet add package MudBlazor |
_Imports.razor |
@using MudBlazor |
Components/App.razor |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <base href="/" /> <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" /> <!-- ← --> <link href=@Assets["_content/MudBlazor/MudBlazor.min.css"] rel="stylesheet" /> <!-- ← --> <link href=@Assets["app.css"] rel="stylesheet" /> <link href=@Assets["JobListBlazor.styles.css"] rel="stylesheet" /> <ImportMap/> <link rel="icon" type="image/png" href="favicon.png"/> <HeadOutlet @rendermode="InteractiveServer" /> <!-- ← --> </head> <body> <Routes @rendermode="InteractiveServer" /> <!-- ← --> <script src="_framework/blazor.web.js"></script> <script src=@Assets["_content/MudBlazor/MudBlazor.min.js"]></script> <!-- ← --> </body> </html> |
Program.cs |
builder.Services.AddMudServices(); // after CreateBuilder |
Components/Layout/MainLayout.razor |
@inherits LayoutComponentBase <MudThemeProvider Theme="@theme" IsDarkMode="isDarkMode" /> <MudPopoverProvider /> <MudDialogProvider /> <MudSnackbarProvider /> <MudLayout class="@RootCssClass"> <MudAppBar Elevation="1"> <MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@((e) => DrawerToggle())" /> <MudTooltip Text="@($"Version {assemblyVersion}")" Placement="Placement.Right" Arrow="true" Duration="1000"> <MudText Typo="Typo.h5" Class="ml-3">ToDo Jobs</MudText> </MudTooltip> <MudSpacer /> <MudIconButton Icon="@(DarkLightModeButtonIcon)" Color="Color.Inherit" OnClick="@DarkModeToggle" /> <MudIconButton Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Edge="Edge.End" /> </MudAppBar> <MudDrawer id="nav-drawer" @bind-Open="drawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2"> <NavMenu /> </MudDrawer> <MudMainContent Class="pt-16"> <MudContainer MaxWidth="MaxWidth.Large"> @Body </MudContainer> </MudMainContent> </MudLayout> <div id="blazor-error-ui" data-nosnippet> An unhandled error has occurred. <a href="." class="reload">Reload</a> <span class="dismiss">🗙</span> </div> |
Components/Layout/MainLayout.razor.cs |
public partial class MainLayout : LayoutComponentBase { private readonly string assemblyVersion = typeof(MainLayout).Assembly.GetName().Version?.ToString() ?? "0.0.0"; private readonly MudTheme theme = new() { PaletteLight = new() { Black = "#110e2d", AppbarText = "#424242", AppbarBackground = "rgba(255,255,255,0.8)", DrawerBackground = "#ffffff", GrayLight = "#e8e8e8", GrayLighter = "#f9f9f9", }, PaletteDark = new() { Primary = "#7e6fff", Surface = "#1e1e2d", Background = "#1a1a27", BackgroundGray = "#151521", AppbarText = "#92929f", AppbarBackground = "rgba(26,26,39,0.8)", DrawerBackground = "#1a1a27", ActionDefault = "#74718e", ActionDisabled = "#9999994d", ActionDisabledBackground = "#605f6d4d", TextPrimary = "#b2b0bf", TextSecondary = "#92929f", TextDisabled = "#ffffff33", DrawerIcon = "#92929f", DrawerText = "#92929f", GrayLight = "#2a2833", GrayLighter = "#1e1e2d", Info = "#4a86ff", Success = "#3dcb6c", Warning = "#ffb545", Error = "#ff3f5f", LinesDefault = "#33323e", TableLines = "#33323e", Divider = "#292838", OverlayLight = "#1e1e2d80", }, LayoutProperties = new LayoutProperties { DrawerWidthLeft = "160px" } }; private bool drawerOpen = true; private bool isDarkMode = true; private string DarkLightModeButtonIcon => isDarkMode switch { true => Icons.Material.Rounded.AutoMode, false => Icons.Material.Outlined.DarkMode, }; private string RootCssClass => isDarkMode ? "dark-mode" : ""; private void DrawerToggle() { drawerOpen = !drawerOpen; } private void DarkModeToggle() { isDarkMode = !isDarkMode; } } |
![]() |
Ensure app.MapStaticAssets() is well called in Program.cs |