Bootstrap is all good and graceful, but let's go for different cuisine. MudBlazor is an open-source library that you want to look at if you are into Material Design. The community is welcoming, and they are pushing new updates every once in a while.

MudBlazor - Blazor Component Library
Blazor Component Library based on Material Design. MudBlazor is easy to use and extend, especially for .NET devs because it uses almost no Javascript.

Consider this post as an intro to MudBlazor. The default template for a Blazor WebAssembly comes in with some layouts and pages. Although our flashcards application doesn't require these boilerplate markups, I'm keeping them for the time being. I'm going to try out some MudBlazor components on these pages and layouts.

After creating a Blazor WASM application, you would need to install the NuGet package for MudBlazor,

dontet new blazorwasm --no-https

dotnet add package MudBlazor

Inside _Imports.razor, add the following,

@using MudBlazor
_Imports.razor

Add the Roboto font-face & MudBlazor style references in index.html,

<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />

While you are at it, delete the references of bootstrap.min.css and app.css files. Also, delete MainLayout.razor.css & NavMenu.razor.css as we want to start from scratch.

You can delete the css folder under wwwroot altogether. We will add a custom css file whenever we need one.

Add the script file reference for MudBlazor inside the body right after _framework/blazor.webassembly.js

<script src="_content/MudBlazor/MudBlazor.min.js"></script>

Register the MudBlazor services with the built-in IoC container. For Blazor WASM, Program.cs class is the place where you register your services,

public static async Task Main(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    
    /* Code removed for brevity */
    
    builder.Services.AddMudServices();
    
    await builder.Build().RunAsync();
}
Program.cs

Some MudBlazor components need initialization beforehand. Here are the components you need to add in the MainLayout.razor,

<MudThemeProvider />
<MudDialogProvider />
<MudSnackbarProvider />

Hot Reload

This feature is still in development as of now. Might not work in some cases.

Before moving forward, I would like to point out a new feature in .NET 6.0 called Hot Reload.

Hot reload is used to refresh only the file in which code is changed while preserving the application state

In every SPA framework (React/Angular/Vue), this feature is available. It is hugely beneficial if you think from a designer's perspective. You don't have to stop and start an application every time a tiny design change is needed. To enable Hot Reload in a Blazor WASM app, in your launchSettings.json add the following lines under the profiles tag,

"FlashCardsWasm": {
    "commandName": "Project",
    "hotReloadProfile": "blazorwasm"
    
    /* Code removed for brevity */
}
launchSettings.json
To try out hot reload with an existing ASP.NET Core project based on .NET 6, add the "hotReloadProfile": "aspnetcore" property

Next up, you would want to run your applciation in watch mode using the following command,

dotnet watch run

And you will never have to stop and start your application form now on for a small change.

Wire Frames

There are some ready to use wireframes from MudBlazor. These are some fequently seen layouts on the web built with different MudBlazor components like MudLayout, MudAppBar, MudDrawer and MudMainContent.  Replace the exisiting markup in the MainLayout.razor with the following,

There are some ready-to-use wireframes from MudBlazor. These are frequently seen layouts on the web built with different MudBlazor components like MudLayout, MudAppBar, MudDrawer, and MudMainContent. Replace the existing markup in the MainLayout.razor with the following,

<MudLayout>
    <MudAppBar Elevation="1">
        <MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start"
            OnClick="@((e) => DrawerToggle())" />
        <MudAppBarSpacer />
        <MudIconButton Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Edge="Edge.End" />
    </MudAppBar>
    <MudDrawer @bind-Open="_drawerOpen" Elevation="2">
        <MudDrawerHeader>
            <MudText Typo="Typo.h5">Flash Cards</MudText>
        </MudDrawerHeader>
        <MudDivider />
        <NavMenu />
    </MudDrawer>
    <MudMainContent Class="px-8" MaxWidth="MaxWidth.False">
        @Body
    </MudMainContent>
</MudLayout>

@code {
    bool _drawerOpen = true;

    void DrawerToggle()
    {
        _drawerOpen = !_drawerOpen;
    }
}
MainLayout.razor

Moving on to the NavMenu component, I'm using MudNavMenu and MudNavLink. MudNavLink automatically assigns an active class on a selected menu item based on the current route.

 <MudNavMenu>
    <MudNavLink Href="" Match="NavLinkMatch.All" Icon="@Icons.Filled.Dashboard" IconColor="Color.Inherit">Home</MudNavLink>
    <MudNavLink Href="counter" Icon="@Icons.Filled.Timer" IconColor="Color.Inherit">Counter</MudNavLink>
    <MudNavLink Href="fetchdata" Icon="@Icons.Filled.FileDownload" IconColor="Color.Inherit">Fetch data</MudNavLink>
</MudNavMenu>
NavMenu.razor

For structuring individual pages, try out the MudGrid. Starting with index.razor page,

@page "/"

<MudGrid Class="mt-4">
    <MudItem xs="12" sm="12" md="12">
        <MudText Typo="Typo.h3">Hello, world!</MudText>
        <MudText Typo="Typo.subtitle1">Welcome to your new app.</MudText>
    </MudItem>
</MudGrid>
Index.razor

With xs, sm and md, you define how many units in the grid the MudItem will take based on different display real-estates. To stay consistent with the typography, MudText has been used with different Typo attributes.

Same thing goes for the Counter.razor page. Here, we have a new MudButton component,

@page "/counter"

<MudGrid Class="mt-2">
    <MudItem xs="12" sm="12" md="12">
        <MudText Typo="Typo.h3">Counter</MudText>
        <MudText Typo="Typo.subtitle1">Current count: @currentCount</MudText>
    </MudItem>
    <MudItem xs="12" sm="12" md="12">
        <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="IncrementCount">Click me</MudButton>
    </MudItem>
</MudGrid>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}
Counter.razor

For FetchData.razor page, I'm using the MudTable. It has cool features like binding items directly to the Items attribute instead of having to create a foreach loop and a progress bar. Also it chages its layout based on the screen width.

@page "/fetchdata"
@inject HttpClient Http

<MudGrid Class="mt-4">
    <MudItem xs="12" sm="12" md="12">
        <MudText Typo="Typo.h3">Weather forecast</MudText>
        <MudText Typo="Typo.subtitle1">This component demonstrates fetching data from a service.</MudText>
    </MudItem>
    <MudItem xs="12" sm="12" md="12">
        <MudTable Items="@forecasts" Hover="true" Breakpoint="Breakpoint.Md" Loading="@(forecasts == null)"
            LoadingProgressColor="Color.Info">
            <HeaderContent>
                <MudTh>Date</MudTh>
                <MudTh>Temp. (C)</MudTh>
                <MudTh>Temp. (F)</MudTh>
                <MudTh>Summary</MudTh>
            </HeaderContent>
            <RowTemplate>
                <MudTd DataLabel="Date">@context.Date</MudTd>
                <MudTd DataLabel="TempC">@context.TemperatureC</MudTd>
                <MudTd DataLabel="TempF">@context.TemperatureF</MudTd>
                <MudTd DataLabel="Summary">@context.Summary</MudTd>
            </RowTemplate>
        </MudTable>
    </MudItem>
</MudGrid>

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
    }

    public class WeatherForecast
    {
        public DateTime Date { get; set; }

        public int TemperatureC { get; set; }

        public string Summary { get; set; }

        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }
}
FetchData.razor

The last bit I wanna talk about is wiring up your modified theme to the <MudThemeProvider/>. You can create an instance of MudTheme and add different Palette, Shadows, Typography, Layout Properties, and z-index for your components to use universally.

Here is how you should do it in the MainLayout.razor,

<MudThemeProvider Theme="_customTheme" />

@code {
	readonly MudTheme _customTheme = new MudTheme()
    {
        Palette = new Palette()
        {
            Primary = "#de5c9d",
            Secondary = "#0d6efd",
            AppbarBackground = "#6f42c1",
            Error = "#dc3545",
            Success = "#198754",
            Info = "#0d6efd"
        },

        LayoutProperties = new LayoutProperties()
        {
            DrawerWidthLeft = "320px"
        }
    };
}
MainLayout.razor

Part I - https://github.com/fiyazbinhasan/FlashCardsWasm/tree/Part-I-Hot-Reload-And-MudBlazor

fiyazbinhasan/FlashCardsWasm
Contribute to fiyazbinhasan/FlashCardsWasm development by creating an account on GitHub.
fiyazbinhasan/FlashCards
Learning Blazor By Creating A Flash Cards Application - fiyazbinhasan/FlashCards
.NET 6 Preview 3 Furthers ‘Hot Reload Everywhere’ Push for Blazor, More -- Visual Studio Magazine
.NET 6 Preview 3 includes early Hot Reload support for ASP.NET Core/Blazor web apps, furthering a push to make it available across the entire tooling gamut.