Implementing Dark Mode in SCSS

Posted on October 30, 2024

After defining the basic theme colors for my website, I thought about adding a dark mode, as it shouldn’t be too hard to do using SCSS. Amazingly, thanks to CSS getting new features such as variables, this was even easier than I thought, the only real reason to use SCSS here is because I want the site to display dark/light mode depending on system settings without JavaScript, and SCSS makes it easier to bundle all the theme’s styles in one place.

Defining the Theme §

We define the themes using mixins. Most of the rules in these mixins are colors, however, I have also defined some extra rules, such as inverting the colors of the tikzpictures in dark mode to ensure they are readable on a dark background. It also includes a rule for a class .theme-icon, for use in a toggle switch to switch the theme (it looks like this: ). I have also adjusted a bunch of colors in the syntax highlighting to ensure that the source code blocks are readable using mostly the same colors in dark and light mode.

@mixin light-theme {
	--text_color: #000;
	--body_background_color: #ffeeff;
	--source_background_color: #ffeeee;
	--footer_color: #555;
	--highlight_color: #707;
	--secondary_color: #a10;
	.tikzpicture {
		filter: none;
	}
	.theme-icon::after {
		content: "🌙"
	}
}

@mixin dark-theme {
	--text_color: #fff;
	--body_background_color: #000022;
	--source_background_color: #110000;
	--footer_color: #aaa;
	--highlight_color: #a0f;
	--secondary_color: #e40;
	.tikzpicture {
		filter: invert(1);
	}
	.theme-icon:after {
		content: "☀️"
	}
}

Implementing Theme Switching §

We will implement the theme switching using the techniques outlined in [Adhu24]. The following CSS code ensures the theme is set up to match the system theme, but can be overriden by giving the body element the .dark_mode or .light_mode class using JavaScript.

:root {
	@include light-theme;
}

.dark_mode {
	@include dark-theme;
}

@media (prefers-color-scheme: dark) {
	:root {
		@include dark-theme;
	}
	.light_mode {
		@include light-theme;
	}
}

Toggling Themes §

Toggling themes is done using a small piece of JavaScript. We store the user’s theme selection in local storage, and retrieve it when loading the page to setup the user’s theme choice correctly. We also set the body class to the correct theme if there is no theme stored in local storage, but there is a system preference.

const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");
const userTheme = localStorage.getItem("theme");
if (userTheme == "dark") {
	document.body.classList.add("dark_mode");
} else if (userTheme == "light") {
	document.body.classList.add("light_mode");
} else if (prefersDarkScheme.matches) {
	document.body.classList.add("dark_mode");
} else {
	document.body.classList.add("light_mode");
}

Now, we get to the part of actually implementing the toggler. We do this from the ground up in JavaScript, so the toggle is not displayed if it doesn’t do anything due to lack of JavaScript.

const toggle = document.createElement("a");
toggle.id = "toggle-button";
toggle.href = "javascript:;";
toggle.title = "Toggle Light/Dark Mode";
const icon = document.createElement("span");
icon.classList.add("theme-icon");
toggle.appendChild(icon);
document.getElementById("nav").appendChild(toggle);

Now comes the actual toggling logic, it just toggles the body’s css classes and stores whichever class is active into local storage to be able to retrieve it later.

function toggle_scheme () {
		let theme1 = "light";
		let theme2 = "dark";
		document.body.classList.toggle(theme1+"_mode");
		document.body.classList.toggle(theme2+"_mode");
		let theme = document.body.classList.contains(theme1 + "_mode") ? theme1 : theme2;
		localStorage.setItem("theme", theme);
}
toggle.addEventListener("click", function () {
		toggle_scheme();
});

References §

[Adhu24]
Adhuham: Dark mode in CSS guide. CSS-Tricks.