Respecting Theme Preferences on Your Website
• 1764 words
• 9 minute read
You may have noticed that dark themes are becoming more and more common across the computing landscape. Everything from Windows 10, macOS Mojave and later, iOS 13+, and Android 10+ to many Linux desktop environments and many individual browsers are including dark/light theme toggle settings.
In addition, you may have noticed that some websites are now starting to respect your OS and browser dark mode settings. For example, StackOverflow now can detect whether your browser or OS has dark mode enabled.
Because I love dark mode, I decided to see if I could make this website adhere to the browser/OS settings for dark and light themes. Here's a demo of what I was able to achieve:
Change theme to:
In this post, I'm going to share what I learned while implementing this for my website. Hopefully this helps you implement light/dark theme adaptation for your website.
Three Tips for Creating a Dark Theme for your Website
The first thing you want to do is to actually create a dark theme for your website. Obviously, each site will have unique challenges in designing a dark theme, but here are a few things that I've found useful as I designed the dark theme for this website.
Don't use pure black and white. Ever. It may be tempting to just use a pure black and pure white in your dark theme, but ends up being fairly unreadable. Take the following examples:
This text is very unreadable because it has too much contrast.
The background-color is black and the color is white.
All of the letters kinda run together because your brain is concentrating on the high amount of contrast with the background. It's difficult for your brain to understand what's going on.
This text is much more readable because the contrast is not so stark.
The background-color is #222 and the color is #ddd.
The letters stand out on the page distinctly, but the contrast is not distracting. It's much easier for your brain to concentrate on the actual content instead of the contrast with the background.
Using a grey for your background colour rather than pure black also gives you the option to use darker greys as accent colors. For example, for this site, if you turn on dark mode (you can turn it on via the links in the footer) the background colour for the content you are reading is #333 and the text colour is #eee. The sidebar on the left has a darker background colour of #222. This allows the sidebar to be darker than the content in both the light and dark themes.
Don't just go and change all the colours. Some colours can be left the same or nearly the same. The link text is one example of a colour that doesn't change between the light and dark themes on this site.
Dim your images. Most of the time, images are naturally bright content which can be jarring in the middle of a dark-themed website. To counteract that, one option that has worked well for me is to dim the images by default. Then, on hover you can un-dim the images (restoring them to their full brightness). In my case, I set images to have a 70% opacity when not hovered, and a 100% opacity (no transparency) when it's hovered. For example, see the following image:
I use this dimming effect in light mode as well (but with only 90% opacity when not hovered) and it creates a nice effect there as well.
Detecting Theme Preference in CSS
There is a CSS standard media query called prefers-color-scheme (MDN) that is currently supported by all major browsers except Internet Explorer. This allows you to add CSS overrides for when the user prefers a dark theme. For example, if you have a very basic text site that has black text on a white background by default, and white text with a black background in dark mode, you can use the following CSS.
The media query also supports preferred-color-scheme: light or preferred-color-scheme: no-preference if for example, you have a dark-by-default site and you want to override the styles for light themes.
Sometimes, however, you may need to determine programatically what theme preference the user has set. For example, you may want to give them some indication of what theme your website is currently using. To do this, you can take advantage of the window.matchMedia function (MDN) which returns a MediaQueryList object. You can query the object directly:
or you can add a listener that will be called when the value changes:
Obviously, you can pass any function you want to the addListener function, I'm just passing a lambda here for brevity.
When you add a media query listener, it will not be called on page load, it will only be called when the theme actually changes. You will likely want your code to run both when the page loads and when the user changes their theme, so I recommend extracting the theme-dependant logic out to its own function like so:
Allowing Users to Override the Detected Theme on Your Site
First, you have to duplicate your dark-mode styles under a theme class on the <body> and make sure that your dark theme styles don't override the user-chosen light theme. For example, the example above would become something like:
Now, if the user's colour scheme preference is for dark mode or there is a dark-theme class on the <body>, dark mode will be enabled. Additionally, if there is a light-theme class on the <body>, the dark theme styles will not be applied, even if the browser or OS color scheme preference is set to the dark theme.
To avoid manual duplication of your CSS styles (and thus adhering to the DRY principle), you can use a CSS compiler such as SASS, SCSS, or LESS. For example, if you use SCSS you can declare a mixin that includes all your dark-theme styles. Then, you can @include your mixin as the styles for both the media-query-based and class-based dark theme detection strategies. The example above would become:
In this simple example, using the mixin actually adds code, but when you have many style overrides for your dark theme, it can greatly inprove your code maintainability.
Now, if you want to switch the theme programatically, all you have to do is change the themeOverride value in localStorage and call the handleTheme function. You can do this in any way you want.
For example, this website provides a set of links in the footer which call a switchTheme function with the desired theme.
Here are a few other resources that helped me as I was implementing dark mode for this website and as I was writing this article.