Implementing dark mode in Blazor and Tailwind CSS Part One


If you have been following along with the last few weeks of posts you will have noticed that I have been writing about Blazor and a few tips and tricks that I have come across while rebuilding glennhevey.com. Today I thought I would round out these series of posts discussing how I implemented dark mode on my site. This post will be a two part series, part one will focus on the backend code needed to build a flexible theming system on your site.

Dark Mode

Dark mode is one of those features that has become a staple for any site. Users have become accustomed to applications, websites and even operating systems to offer theme options.

For websites there is multiple ways to handle dark mode. We will be implementing options for both the user to manually pick the theme or to use the browser to decide which theme to pick.

If you are not using Tailwind CSS and want to theme your site with vanilla CSS, You can use the media query prefers-color-scheme: and example of using this in CSS is below.

color: black;
background-color: white;

@media (prefers-color-scheme: dark) {
	color: white;
	background-color: black;
}

However as glennhevey.com use Tailwind CSS we will be focussing on this and how it handles setting dark mode themes.

Tailwind and Dark Mode

Tailwind has a great way to set the dark mode setting using the dark: prefix. The prefix can be added to any Tailwind attribute and will be used when dark mode is activated. Below is an example for setting text colour to white when dark mode is enabled.

<NavLink href=@postLink Match="NavLinkMatch.All">
	<p class="dark:text-white font-bold my-0">Read More</p>
</NavLink>

While using the dark: prefix is enough if you are happy to allow the theme for your site to be picked by the browser. This won’t allow for manual theme picking. To do this we need to tell Tailwind to use a class to set its dark mode.

In your tailwind.config.js set the darkMode option to class.

module.exports = {
    mode: 'jit',
    purge: [
        '../Server/Pages/_Host.cshtml',
        './**/*.razor'
    ],
    darkMode: 'class', // this is set to 'media or 'class'
		
		...    
}

Tailwind will then look for a dark mode class in the pages HTML. The easiest place to add this to the <html> tag. This is what we will do today.

Implementing the Dark Mode backend code

To implement our dark mode we will begin by creating a ThemeChoice enum for the choices we will offer.

namespace ThemePickerProject.Models;

public enum ThemeChoice
{
    System,
    Light,
    Dark
}

As you can see we have an option for three options that we will be offering.

  • System: system will be used to let the browser decide the theme.
  • Light: will manually set the theme to the light theme.
  • Dark: will manually set the theme to the dark theme.

Next Will create a Theme model.

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace ThemePickerProject.Models;

public class Theme : INotifyPropertyChanged
{
    private ThemeChoice _choice;
    public ThemeChoice Choice
    {
        get => _choice;
        set => SetField(ref _choice, value);
    }


    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged(string? propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    private void SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return;
        field = value;
        OnPropertyChanged(propertyName);
    }

}

Now there is a lot going on here but let’s break it down. The Theme model is there for two reasons. To hold the user’s theme choice and fire and event when this choice changes.

To begin we have created a class called Theme that implements the INotifyPropertyChanged interface. This interface creates a PropertyChangedEventHandler this will be what we subscribe to when we want to be notified by changes to the theme choice.

public class Theme : INotifyPropertyChanged
{

... 

public event PropertyChangedEventHandler? PropertyChanged;

...
}

Next we have to create a property to hold the theme choice. Now we would normally create the public property but because we want to trigger a PropertyChangedEvent when this changes we need a private backing field where the value is held.

public class Theme : INotifyPropertyChanged
{

private ThemeChoice _choice;
public ThemeChoice Choice
{
    get => _choice;
    set => SetField(ref _choice, value);
}

public event PropertyChangedEventHandler? PropertyChanged;

...
}

As you can see the public property Choice returns a copy of the private _choice field. We need this to fire the even when the property changes. Both of these are the ThemeChoice enum type we created.

We are calling the SetField() method when the public property is changed.

To read more on class fields check out Microsoft’s documentation

public class Theme : INotifyPropertyChanged
{
    private ThemeChoice _choice;
    public ThemeChoice Choice
    {
        get => _choice;
        set => SetField(ref _choice, value);
    }


    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged(string? propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    private void SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return;
        field = value;
        OnPropertyChanged(propertyName);
    }

}

Now the SetField method is a generic method that can take any property type. This class has one property type of ThemeChoice at the moment which means that we don’t need to use a generic method but if you extend this model to hold other data later, then you would need to write multiples of this method. It is a good idea in this case to use a generic method in the first place to make extending easier later.

The SetField method has three parameters.

  • field: This holds a reference to the private field type that we will be setting.
  • value: This holds the value that we want to set the field parameter to.
  • propertyName: the properName parameter is a special parameter as it is used to get the details of the property that has called this method. This uses the [CallerMemberName] attribute. As we aren’t setting this directly it needs a default value. We are using null here but you could set it to anything. You can read more about the attribute on the Microsoft Docs website.

The method is pretty simple, let’s break it down. The first line checks to see whether the value that is being set is the same as the current value of the property we are going to set, if it is we return from the method.

If the value has changed we then set the field to the passed in value and call the OnPropertyChanged method passing in the propertyName (in this case it will be ‘Choice’).

The OnPropertyChanged method is used to invoke the the PropertyChanged event. Later you will see that we set up methods to subscribe to this event. When we invoke this event our subscriptions will receive an event and we can then call a method to do something. You will see this in Part Two when we build a theme picker.

This post went through setting up the pieces needed to implement dark mode in your Blazor and Tailwind CSS projects. Part Two of this post series will run through creating a theme picker, storing the users theme choice, interacting with the DOM via javascript and loading the theme choice on page load.