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 |