How to Create a Dark Mode Toggle Button

In this post, I will show how you can build a dark mode switch for your website. A dark mode is a web design trend that started a few years ago. It’s not the end of the world if your website doesn’t have it, but there are reasons why you might want to consider adding it.

Why do you need a dark mode in the first place?

The foremost reason for having a dark mode is that it’s cool and trendy, and all the modern blogs have it, so if you don’t want to be left behind, you should have it too. Joking aside, you can get by without a dark mode just fine, but having a dark mode on your blog also brings a lot of practical benefits.

Let’s build the switch

The following section is divided into three parts. The first part shows how to create the markup for the toggle button, then the second part defines the style, and finally, the third part will be the JavaScript code that will make the button work.

Markup

Let’s start building the switch with the markup first. As it will be an interactive element, the HTML button element should be used as a starting point. The button will only need a single class attribute for styling and identification.

<button class="theme-toggle"></button>

Next, the button needs two icons to represent the two different theme states—light and dark.

Where to get the icons? I was a long-time user of the Font Awesome icon library, but the recent influx of free and open source SVG icon libraries made me to switch. There are many of them available for free, but this time I recommend using and from the excellent library of SVG icons called Tabler Icons. Any other SVG icon from another icon set should work as well.

Below is how the final markup would look after putting the icons inside the button. Please note that I also changed the class attribute for both icons. I’ve simplified the original Tabler class names to just sun and moon.

<button class="theme-toggle">
    <svg xmlns="http://www.w3.org/2000/svg" class="light" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
        <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
        <path d="M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" />
        <path d="M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7" />
    </svg>
    <svg xmlns="http://www.w3.org/2000/svg" class="dark" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
        <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
        <path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" />
    </svg>
</button>

The markup is finished. Let’s move on to style the button.

Style

The stylesheet will start with variables to hold color values for the different toggle button states and the different page themes. The first CSS rule in the example below will define the default or the light color scheme, and the second one will overwrite those values with color values for the dark theme.

:root {
    --nav-link-color: #475569;
    --nav-link-hover-color: #64748b;
}

html[data-theme=dark] {
    --nav-link-color: #94a3b8;
    --nav-link-hover-color: #cbd5e1;
}

The following code block will remove the default style from the button. Without default styling, the button will not appear as button anymore, so it should have a pointer cursor to make a hint to the user that it is a clickable element.

.theme-toggle {
    background-color: transparent;
    border: none;
    cursor: pointer;
    margin: 0;
    padding: 0;
}

Next come the icons that represent the state of the toggle button. The icons need to be colored according to the current theme. Also, the sun icon needs to be hidden in dark mode and the moon icon needs to be hidden when a light theme is selected.

.theme-toggle svg {
    stroke: var(--nav-link-color);
}

.theme-toggle:hover svg {
    stroke: var(--nav-link-hover-color);
}

html[data-theme=light] .theme-toggle .light {
    display: none;
}

html[data-theme=dark] .theme-toggle .dark {
    display: none;
}

JavaScript

The script for the toggle button is pretty straight forward. When the page loads for the first time, it tries to detect if a user has requested the dark theme, and then changes the theme accordingly. If the user prefers the light theme, it does nothing as the light theme is the default theme.

Also, when the user clicks the button, the script changes the current theme and persists the new theme to the local storage. It is done so that the next time the user loads the page, the last selected theme is used.

Setting the theme according to the user’s operating system preference

You can determine the user’s preferred color scheme by querying the ‘prefers-color-scheme’ media feature using the Window interface’s matchMedia method. Assuming that the default color scheme is light, you will need to change the theme only if the user’s preferred color scheme is dark, or the theme value in the local storage is not empty and equal to ’light’.

if (localStorage.getItem('theme') !== null) {
    document.documentElement.dataset.theme = localStorage.getItem('theme')
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
    document.documentElement.dataset.theme = 'dark'
}

Changing the theme when the toggle button is clicked

document.querySelector('.theme-toggle').addEventListener('click', () => {
    document.documentElement.dataset.theme = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'
    localStorage.setItem('theme', document.documentElement.dataset.theme)
})

Bonus feature: automatically react to the theme change on the visitor’s computer

When you use the matchMedia method to check if the document matches the media query string, you can also attach an event listener to it and get notified when a change occurs.

You can utilize this event to make the theme change on your website when a user changes the preferred color scheme on their computer.

Below is the complete code of the script that does the exact thing I described above.

If you think that this step is unnecessary, then just skip it.

window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
    document.documentElement.dataset.theme = e.matches ? 'dark' : 'light'
    localStorage.setItem('theme', document.documentElement.dataset.theme)    
})

Conclusion

Having a dark theme on your website will give convenience to your visitors, who may have different reasons for preferring a dark theme over a light one.

You can find the complete code from this article here.

References