In the previous post, I explained how to automatically set a dark theme using a CSS media query to check site visitors’ device preferences. Next, I will show you how to let users select the site theme by writing a few JavaScript functions.

You can also check out the demo if you wish to skip the whole guide to show you how it works.

If you have followed the guideline to set a dark theme before, you will notice that we’ve used a :not() pseudo class to apply the dark theme when the :root element doesn’t contain data-site-theme as the attribute.

@media (prefers-color-scheme: dark) {
	:root:not([data-site-theme]) {
		--theme-color-background-400: #{$color-charcoal-400};
		--theme-color-background-500: #{$color-charcoal-300};
		// The rest of the dark theme color tokens
	}
}

In HTML, :root represents the <html> element and is identical to the selector html, except that its specificity is higher.

Building a feature for site visitors to select the site theme requires a few requirements:

  1. We need a way to store the user option.
  2. We need to add some UI elements for users to choose their preferred themes.
  3. We need to update the CSS to support the selected theme.

Creating Theme Manager Object

One of my favorite design patterns is Command Pattern, where you can encapsulate actions as objects. For me, it’s the most straightforward design pattern to learn if you’re new to JavaScript because you’re only dealing with methods to manipulate DOM. For example, here is the complete snippet to create a theme manager that I use on my site.

const _manager = {
	setTheme: function (userColorScheme) {
		localStorage.setItem('user-color-scheme', userColorScheme);
		document
				.querySelector('html')
				.setAttribute('data-site-theme', userColorScheme);
	},
	setUserTheme: function () {
		if (
			localStorage.getItem('user-color-scheme')
		) {
			let userColorScheme = localStorage.getItem('user-color-scheme');
			this.setTheme(userColorScheme);
		}
	},
	resetUserTheme: function() {
		localStorage.removeItem('user-color-scheme');
		document
				.querySelector('html')
				.removeAttribute('data-site-theme');
	},
	init: function () {
		this.setUserTheme();
	},
};

_manager.init();

Let’s take a look at each method and its purpose.

setTheme

setTheme is the method to set the site theme. Using the built-in localStorage property, you create a local item called user-color-scheme with the value you pass into the method. You then use querySelector to apply the value to data-site-theme attribute. Here is an example of an updated html data attribute when you run _manager.setTheme('dark').

<html lang="en" data-site-theme="dark">

Checking Local Storage — You can check the local storage value using Chrome Dev Tools and navigate to the Application tab. If you’re using Safari, you can check the Storage tab.

setUserTheme

The method’s purpose is to apply a user theme if we detect any values in the local storage. First, we check if there is any value by using localStorage.getItem('user-color-scheme') function. If we detect a value, we will apply them by calling the setTheme method we built earlier.

setUserTheme: function () {
	if (
		localStorage.getItem('user-color-scheme')
	) {
		let userColorScheme = localStorage.getItem('user-color-scheme');
		this.setTheme(userColorScheme);
	}
}

resetUserTheme

Although it’s nice to allow site visitors to choose their theme, we also need to offer a way for them to reset their choice for the site to match their device’s theme setting. This method will remove user-color-scheme item from the local storage and remove the data-site-theme attribute from the document.

resetUserTheme: function() {
	localStorage.removeItem('user-color-scheme');
	document
			.querySelector('html')
			.removeAttribute('data-site-theme');
}

Buttons for Theme Selection

With the theme manager ready to use, we can pass the methods into onclick event to set the user theme. You can wrap the SVG icon with the button element if you plan to use icons. Make sure to add aria-label attribute so the screen reader can read the button description.

Here is the structure of the buttons. Replace SVG Icon with the actual SVG value you can copy from free SVG icons sites like Feather Icons.

<aside class="select-theme">
	<button
		onclick="_manager.resetUserTheme()"
		aria-label="Enable System Theme"
	>
		SVG Icon
	</button>
	<button
		onclick="_manager.setTheme('light')"
		aria-label="Enable Light Theme"
	>
		SVG Icon
	</button>
	<button
		onclick="_manager.setTheme('dark')"
		aria-label="Enable Dark Theme"
	>
		SVG Icon
	</button>
</aside>

Following the guide above, you should have functional buttons that allow you to update your site theme. I didn’t cover how to style the buttons because it’s beyond the scope of this guide. I only want to show you how I put the structure together by taking inspiration from other sites.