The Simplest Way to Implement Light / Dark Mode with Sass

Posted on 20 January 2022 — Tech

Hey all, happy new year andā€¦

I finally did it! Iā€™ve implemented something Iā€™ve wanted to do for a very long time - a light and dark mode for this website! It will now listen to your systems preferences and show you corresponding themes based on the mode you prefer.

Light and Dark theme website
Fancy light and dark mode!

This website was originally done in dark theme (because iā€™m #TeamDark), and Iā€™m super pleased to offer it in light theme. But the part that excites me the most is that it was done purely with CSS, and over my lunch break (~ 40 minutes). Much easier than I originally expected. Most of that time was spent researching an implementation that works for me. The actual coding part took maybe 10 minutes. Why didnā€™t I do this sooner?

Before I forget everything, Iā€™m writing down what Iā€™ve learnedā€¦ I hope it will be useful for you in some ways.

prefers-color-scheme

Switching themes based on system preferences is made possible by a neat CSS media feature called prefers-color-scheme. You can read more about it over here, but the gist of it is that you can just do:

@media (prefers-color-scheme: dark){
	\\ CSS here
	...
}

This will execute the CSS contained within if a user prefers the dark color scheme! Magic āœØ

Neat and easy right? Well, if I was coding in pure CSS that would be the end of it, Iā€™d simply override all the color properties from my original theme and call it a day. But what lunatic codes in pure CSS these days? The system Iā€™m using to run this site (Jekyll) uses Sass. I had already done the work of separating all the colors into proper color scheme and variables, precisely because I anticipated implementing the light/dark mode switcher some time in the future. I wasnā€™t just going to throw all that work away.

So I started to look for solutions that ticks my requirement:

  • I want the simplest possible solution. I want to use the prefers-color-scheme feature. I donā€™t want to overcomplicate things by using additional JavaScript.
  • Works with Sass / Jekyll and I donā€™t have to do any further configurations.

My Sass / CSS Files Organization

To understand the rest of the post, itā€™s important to know how Iā€™ve structured my Sass files. First of all keep in mind that this website is very simple, so the way my Sass was coded is simple too: I just have one main Sass file called styles.scss, which contains all the styling I need.

For some organization, I used two partials:

@import "variables";
@import "themes";

Where variables contains variables for things like spacing, width, font-sizes, font-weights, etc. On the other hand, themes contains all the color variables for every hex I used on the website. I created variables for base color, which contains hex codes for all the colors used on the website, and I also have other color variables, which controls colors in the website but simply references the base color. Like so:

/* Base Colors */
$c-white: #FFFFFF;
$c-light: #DFE2ED;

...

/* Other Colors */
$c-background: $c-dark;
$c-pageborder: $c-darker;

...

An approach I considered: @import

Before I did any research, I thought it would be great if I could just use prefers-color-scheme and switch which theme file to include. Thanks to Jim Nielsenā€™s article, I found that it IS possible to do this:

@import "dark.css" screen
and (prefers-color-scheme: dark);

This would have been the cleanest way, if I could just switch which Sass partials to import.

Unfortunately, while it works for pure CSS, this approach would not work with Sassā€¦ or at least with how I had mine configured. I realized that simply importing a partial with variables for the theme color would not make the theme dynamic. Sass is a pre-processor, so it simply compiles CSS with what itā€™s given at the time of execution. It does not know what to do with the variables itā€™s switched when the CSS was compiled.

The final approach I used: A mix of CSS Variables and Sass variables

Further down my research, I stumbled upon this stackoverflow question, which gave me ideas for the approach that I ended up using - CSS Variables! It ticked all the boxes in my requirements, so I got to work to implement it.

In styles.scss, I still do the same thing - I imported my two partials:

@import "variables";
@import "themes";

But I updated _themes.scss so that it contains both the light and dark theme colors:

:root {

  /** LIGHT THEME */

  /* Base Colors */
  $c-white: #FFFFFF;
  $c-light: #e3e6e1;

  ...

  /* Other Colors */
  --c-background: #{$c-light};
  --c-pageborder: #{$c-white};

  ...
}

 /** DARK THEME */

@media (prefers-color-scheme: dark){
   :root {

      /* Base Colors*/
      $c-white: #FFFFFF;
      $c-light: #DFE2ED;
      
      ...

      /* Other Colors */
      --c-background: #{$c-dark};
      --c-pageborder: #{$c-darker};

      ...

  }
}

It defaults to the light theme, and then using prefers-color-scheme, it calls appropriate dark theme color variables should a user prefers dark.

Note that Iā€™m using both Sass variables (anything that begins with $) and CSS variables (anything that begins with --). This is because I wanted to generate a more efficient CSS. At point of execution, Sass variables will not be compiled into CSS, so it helps keeping your resulting CSS file smaller and cleaner. It could make a difference if youā€™re working on something more than just a simple site.

Anyway, I kept the base colors as Sass variables, because the hex of the base colors are set and does not change. Then all the other color variables, aka things that will change dynamically with the theme, are changed to use CSS variables.

By the way, note that if youā€™re assigning a Sass variable to CSS variable, you need to enclose the Sass variable in ${} (as outlined here), like this:

--css-var: #{$sass-var};

Finally in the main styles.scss file, I updated all color properties to use the CSS variables instead:

body {
    background-color: var(--c-background);
    ...
}

And voilĆ ! If you view this site in each respective mode, the theme will change accordingly.

Iā€™m so pleased ā˜ŗļø

And in Closing Thoughts..

Iā€™m aware that this solution is the simplest way to ā€œdo dark modeā€ - hence the title of this blog post. There could be more elegant solutions out there. But this works for my website, precisely because itā€™s a simple static website, and I donā€™t anticipate it growing any more complicated than this. Thus, Iā€™m happy with this solution!

If this approach doesnā€™t work for you, there are plenty other approaches you can consider, such as using mixin or maps or even JavaScript.

Here are some articles that might help you:

Good luck!

I'm sorry I haven't gotten around to implementing a commenting system yet! If you want to discuss something or have questions, I can be reached via email