Brand new dark mode (wow!)
codeI added theme detection and a theme switcher to my site using CSS variables in my existing SCSS styles
unofficial theme song of this post
"Neptune" by D'allant
dark mode
what I had:
- a (messy) light theme
- somewhat structured SCSS (with color variables etc) compiled to CSS at build time
what I wanted:
- a (less messy) light theme
- a new shiny dark theme
- ability to use my existing SCSS variables in CSS variables (for easy theme switching) without changing anything about my existing SCSS structure
- theme detection and a way to override with a theme switcher
step 1: extract colors into a theme
the SCSS for this site was (and still is) a mess. I generally had some consistent color usage and SCSS variables defined, but step 1 for adding dark mode involved first taking alllllll of the colors I defined & moving them into a single mixin:
@mixin light-theme() {
--background-color: #{$light-gray};
--background-image: #{$grid-bg};
--text-color: #{darken($violet, 15%)};
// etc.
}
I used existing SCSS variables in CSS variables with string interpolation: #{$variable-name}
this process was a little tedious because my existing color usage was disorganized. I created a new CSS variable for every color I encountered in my SCSS, and then at the end I consolidated some of the colors.
step 2: make a new dark theme
my only advice here: don't feel like you have to invert what you have for your existing (light) theme. it's okay to try something a little different.
once I found a few colors that really went together, it was easier to come up with 1:1 replacements for all of my existing theme colors.
@mixin dark-theme() {
--background-color: #040113;
--background-image: none;
--text-color: #ebbdfc;
// etc.
}
step 3: media queries & override class
the great thing about using mixins for the themes is that you can just throw them into these theme media queries & also apply your theme override (with a class name) like this:
// apply light theme as default:
html,
.light-theme {
@include light-theme();
}
// dark theme override:
html.dark-theme {
@include dark-theme();
}
// dark mode detection:
@media screen and (prefers-color-scheme: dark) {
// apply dark theme as default:
html,
.dark-theme {
@include dark-theme();
}
// light theme override:
html.light-theme {
@include light-theme();
}
}
the classes .light-theme
and .dark-theme
are used to specifically override a theme that is set based on theme detection with prefers-color-scheme
.
the next step is to actually apply those override classes with a theme switcher...
step 4: add the theme switcher
I used my existing visitor preferences structure (which I wrote about in my Accessibility animations post) to add a dark theme switcher (e.g., a checkbox that says "dark theme?" where the checked state means dark mode is enabled and the unchecked state means dark mode is not enabled).
this javascript will use local storage to save theme preferences so that preferences can persist throughout the site:
var htmlElem = document.querySelector("html");
var themecheckbox = document.getElementById("dark-theme");
function setThemeClass() {
var isDarkTheme = localStorage.getItem("theme-pref");
if (isDarkTheme === "true") {
htmlElem.classList.remove("light-theme");
htmlElem.classList.add("dark-theme");
themecheckbox.checked = true;
} else {
htmlElem.classList.remove("dark-theme");
htmlElem.classList.add("light-theme");
themecheckbox.checked = false;
}
}
if (!localStorage.getItem("theme-pref")) {
populateStorage();
}
setThemeClass();
themecheckbox.addEventListener("change", () => {
populateStorage();
setThemeClass();
});
step 5: toggle till your heart is content
you can open the settings in the top right corner of the page and check or uncheck the "dark mode?" checkbox & switch between light and dark themes on this site.
by default, the "dark mode?" checkbox will reflect your current prefers-color-scheme
preferences set at your device level. but once you override that value, your override will be saved in your browser's local storage and will persist anywhere on the site.
thanks for reading!~