Setting a page class in your Blazor app

Setting a page class in your Blazor app

Sometimes it is necessary to add page specific styling. Typically I set a page class. In the context of an SPA (Single Page Application) such as Blazor it is not as straight forward. Here is one solution.

Adding a page class

CSS is in a much more manageable place, with the adoption of techniques to create encapsulated modular components, utility classes and grid layouts. However, occasionally I do need to add page specific styles. I find it a handy tool to have and will generally add the class to the <body> named after the page with a p- prefix. e.g.

<body class="p-home">
</body>

Adding a page class in Blazor

I recommend reading a previous post Setting the document title in your Blazor app. We will be extending this technique to add a page class.

You're back? Great, I'll continue.

We are going to use Javascript to add a class to the <body>. Creating a function to call via the JavaScript interop, passing the class name to add.

site.js

var JsFunctions = window.JsFunctions || {};

JsFunctions = {
    setBodyClass: function (className) {
        window.document.body.classList.add(className)
    }
};

The <Document> component we created in the previous post uses the LocationChanged event from the NavigationManager to set the page title. We will use this to add the page class. Adding the following to our original code.

private async void LocationChanged(object sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
{
    await SetTitle(new Uri(e.Location));
    await SetBodyClass(new Uri(e.Location));
}

private async Task SetBodyClass(Uri uri)
{
    var title = uri.Segments.Last();
    if (title == "/") title = "home";
    
    await JsRuntime.InvokeVoidAsync("JsFunctions.setBodyClass", $"p-{title}");
}

A class name is created using the last segment of the current URL, prefixed with p-. If the last segment of the Uri is / then set the value to home. Whenever navigation has occurred this will run and set the <body> class.

The full code of the <Document> now looks like this.

public class Document : ComponentBase, IDisposable
{
    [Inject]
    protected IJSRuntime JsRuntime { get; set; }
        
    [Inject]
    protected NavigationManager NavigationManager { get; set; }
    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    
    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
            NavigationManager.LocationChanged -= LocationChanged;
    }
    
    protected override void OnInitialized()
    {
        NavigationManager.LocationChanged += LocationChanged;
    }
    
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender) return;
        
        await SetTitle(new Uri(NavigationManager.Uri));
    }
    
    private async Task SetTitle(Uri uri)
    {
        var pageName = uri.Segments.Last();
        await JsRuntime.InvokeVoidAsync("JsFunctions.setDocumentTitle", PageTitleGenerator.Create(pageName));
    }
    
    private async Task SetBodyClass(Uri uri)
    {
        var title = uri.Segments.Last();
        if (title == "/") title = "home";
        
        await JsRuntime.InvokeVoidAsync("JsFunctions.setBodyClass", $"p-{title}");
    }
    
    private async void LocationChanged(object sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
    {
        await SetTitle(new Uri(e.Location));
        await SetBodyClass(new Uri(e.Location));
    }
}

Summary

This is a simple example of setting a page CSS class using the URL to create the class name. It actually wasn't too difficult to do, using the LocationChanged event on the NavigationManager. Being able to extend the <Document> component created in an earlier post was an added bonus.