We see the dark theme everywhere today (although the light themes are not that bad). If you have a dark mode set, then you are probably seeing this web in not-so-bright light.

There are lots of ways to implement it. Many people suggest having different stylesheets for different themes. This one is cool, but no – we often don’t have time (or ever will) to set every component’s look in a different stylesheet. Imagine if you need to change it later – quite annoying it will be, am I right?

There’s still a cooler way to make it by using clean vanilla CSS and a similar method to your SCSS files. It just requires proper thinking and the right setup. So, let’s get started!

Be sure you think the right way about the colors!

It is important for you to properly analyze the design of your website. We most often see white as white, black as black, red as red, and that other red as red #2. This is an okay approach if you are going to have only one theme. If you want to give your visitor two or more themes to choose from, then you will have to abstract those colors.

What does it mean? It means that you need to stop seeing solid colors, and instead, start calling colors by their function. White is in the background? It’s not white anymore – it’s background color set to be white. The foreground color is set to be black, and the primary color is red. You get the pattern?

These new names for your colors are going to be the names of variables in your themes. Of course, you can still have your static colors (a color variable that is the same on all themes, like white, black, and red).

Also, I’ll try to follow either the BEM methodology and/or style guidelines for CSS and SASS/SCSS. I advise you to do the same for your variables.

Prepare your HTML

Let’s start by making some simple index.html file for demonstration:

<!DOCTYPE html>
    <link rel="stylesheet" href="style.css">
    <h1 class="color--primary">This is a title</h1>
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis diam tortor, venenatis ac fringilla sit amet,
        lacinia eget mi. Ut in libero ullamcorper, maximus odio nec, pulvinar tellus.</p>
    <button class="btn--primary">Click me for some reason!</button>

You can see that there’s also style.css linked and located at the same where our HTML file is. This file should be created and empty for now.

We have two classes added to our HTML tags:

Now that we have it set up, it’s time to try this out!

CSS way to the dark theme

We will be using CSS variables for storing our colors. Let’s start by creating variables for the default theme in the :root declaration block:

:root {
  --theme-color-primary: #ff0000;
  --theme-color-background: #ffffff;
  --theme-color-foreground: #000000;

If you are not familiar with CSS variables, fear not! They are simply values (like #ff0000) that have a specific name (like --theme-color-primary). It’s common to start custom CSS variables (like the ones we typed here) with a double dash (--). The values of our CSS variables are going to be returned by calling the var(YOUR_VARIABLE_NAME) function, where YOUR_VARIABLE_NAME is…well…your CSS variable name!

Now, let’s add some properties to the classes that we use. Our CSS file should look like this:

:root {
    --theme-color-primary: #ff0000;
    --theme-color-background: #ffffff;
    --theme-color-foreground: #000000;

body {
    color: var(--theme-color-foreground);
    background-color: var(--theme-color-background);

.color--primary {
    color: var(--theme-color-primary);

.btn--primary {
    background-color: var(--theme-color-background);
    color: var(--theme-color-foreground);
    border: 1px solid var(--theme-color-foreground);

.btn--primary:hover {
    background-color: var(--theme-color-primary);
    color: var(--theme-color-background);
    border: 1px solid var(--theme-color-primary);

That’s a lot of var() function calls, but that’s what we want. So, now we have only one line to change when we want to change our primary color, and that is the value of --theme-color-primary in our :root declaration block.

Switching your theme

Can you see where this is going? All we have to do now is use change the values of CSS variables to get a different theme. We can do this in several ways in some real project:

We will be making the query-based theme switch here, as it only takes us one simple media query to do it and it works out-of-the-box. Let’s say that we want to use darker primary color on our dark theme (like #aa0000), and invert the values of our background and foreground colors.

We can do this by simply writing our media query with prefers-color-scheme set to dark and overriding CSS variables with new values into its declaration block:

/* this block will be triggered if the device prefers dark theme */
@media (prefers-color-scheme: dark) {
    --theme-color-primary: #aa0000;
    --theme-color-background: #000000;
    --theme-color-foreground: #ffffff;

Now we have our dark theme implemented, and it will be triggered depending on our device settings!

SCSS/Sass way to the dark theme

The SCSS/Sass way is quite similar to the CSS one (with some differences to the button’s hover effect). We’ll be using SCSS for this, so let’s start with our file:

$color--primary: #ff0000;
$color--background: #ffffff;
$color--foreground: #000000;

body {
  color: $color--foreground;
  background-color: $color--background;

.color--primary {
  color: $color--primary;

.btn--primary {
  background-color: $color--primary;
  color: $color--foreground;

  &:hover {
    background-color: lighten($color--primary, 10%);
    color: $color--background;

To get the ability to switch themes, you’ll have to switch to CSS variables. Here’s more about the proper usage of CSS variables in Sass. Let’s start by changing the values of our Sass variables to var() functions with our future CSS variables as parameters:

$color--primary: var(--color--primary);
$color--background: var(--color--background);
$color--foreground: var(--color--foreground);

Before we go further, we also need to replace our lighten($color--primary, 10%) with proper Sass variable:

// our new variable
$color--primary-lighten-10: var(--color--primary-lighten-10);

// our btn--primary fix
.btn--primary {
  background-color: $color--primary;
  color: $color--foreground;

  &:hover {
    background-color: $color--primary-lighten-10;
    color: $color--background;

These CSS variables are going to be set for each theme separately. You can do this in the same file, or you can specify each theme in its own partial SCSS file (e.g. theme/_default.scss and themes/_dark.scss). The default theme should be set inside of the :root block:

// our default theme
:root {
    --color--primary: #ff0000;
    --color--primary-lighten-10: lighten(#ff0000, 10%);
    --color--background: #ffffff;
    --color--foreground: #000000;

We can add our dark theme the query-based way, as I’ve explained in the subsection in CSS part:

// our dark theme
@media (prefers-color-scheme: dark) {
    --color--primary: #aa0000;
    --color--primary-lighten-10: lighten(#aa0000, 10%);
    --color--background: #000000;
    --color--foreground: #ffffff;

Now, we have our dark theme up & working depending on your device theme preferences!

Going step further

If you use a lot of Sass functions in your code, or you have a lot of repeating values (like we have #aa0000 both on our --color--primary and --color--primary-lighten-10), you can create another set of Sass variables. This set is going to be used to generate all CSS variables and give you the ability to call some common Sass functions in your multi-themed stylesheet. Here’s an example:

// this is the new set of variables
$theme--default--color--primary: #ff0000;
$theme--default--color--background: #ffffff;
$theme--default--color--foreground: #000000;

// default theme
:root {
    // we can only set $theme--default--primary to change all color variants
    --color--primary: $theme--default--color--primary;
    --color--primary-lighten-10: lighten($theme--default--color--primary, 10%);
    --color--primary-lighten-20: lighten($theme--default--color--primary, 20%);
    --color--primary-lighten-30: lighten($theme--default--color--primary, 30%);
    --color--primary-lighten-40: lighten($theme--default--color--primary, 40%);
    --color--primary-lighten-50: lighten($theme--default--color--primary, 50%);

    --color--background: $theme--default--color--background;
    --color--background-10: rgba($theme--default--color--background, 0.1);
    --color--background-20: rgba($theme--default--color--background, 0.2);
    --color--background-30: rgba($theme--default--color--background, 0.3);
    --color--background-40: rgba($theme--default--color--background, 0.4);
    --color--background-50: rgba($theme--default--color--background, 0.5);
    --color--foreground: $theme--default--color--foreground;
    --color--foreground-10: rgba($theme--default--color--foreground, 0.1);
    --color--foreground-20: rgba($theme--default--color--foreground, 0.2);
    --color--foreground-30: rgba($theme--default--color--foreground, 0.3);
    --color--foreground-40: rgba($theme--default--color--foreground, 0.4);
    --color--foreground-50: rgba($theme--default--color--foreground, 0.5);

// new set of variables for dark theme
$theme--dark--color--primary: #aa0000;
$theme--dark--color--background: #000000;
$theme--dark--color--foreground: #ffffff;

// dark theme
@media (prefers-color-scheme: dark) {
    :root {
        --color--primary: $theme--dark--color--primary;
        --color--primary-lighten-10: lighten($theme--dark--color--primary, 10%);
        --color--primary-lighten-20: lighten($theme--dark--color--primary, 20%);
        --color--primary-lighten-30: lighten($theme--dark--color--primary, 30%);
        --color--primary-lighten-40: lighten($theme--dark--color--primary, 40%);
        --color--primary-lighten-50: lighten($theme--dark--color--primary, 50%);

        --color--background: $theme--dark--color--background;
        --color--background-10: rgba($theme--dark--color--background, 0.1);
        --color--background-20: rgba($theme--dark--color--background, 0.2);
        --color--background-30: rgba($theme--dark--color--background, 0.3);
        --color--background-40: rgba($theme--dark--color--background, 0.4);
        --color--background-50: rgba($theme--dark--color--background, 0.5);
        --color--foreground: $theme--dark--color--foreground;
        --color--foreground-10: rgba($theme--dark--color--foreground, 0.1);
        --color--foreground-20: rgba($theme--dark--color--foreground, 0.2);
        --color--foreground-30: rgba($theme--dark--color--foreground, 0.3);
        --color--foreground-40: rgba($theme--dark--color--foreground, 0.4);
        --color--foreground-50: rgba($theme--dark--color--foreground, 0.5);

As you can see, with the proper setup of color variables – we can have the dark theme ready in no time.

There are some minor disadvantages

Now you have a dark theme implemented! Easy, eh? However, there are some obstacles to it.

The query-based theme switch is quite simple. It’s so simple that it doesn’t give your website’s visitors an option to choose its own theme. This can be fixed by implementing both ways of theme switching to your website, where class-based should always override the query-based approach.

The other problem is adding new variables, as it is not only in one place anymore. If you have to add a new variable to your theme, you can easily add it only to your default theme in :root block, but it’s most likely going to be overridden in as many themes as you have.

SCSS/Sass is having yet another issue – lighten(), darken() or any other color-related function cannot be used with variables. However, you can still use these functions by setting their value to a specific variable (like we did with $color--primary-lighten-10 in the example above).

Hope this way helps. Feel free to leave a comment or suggestion in the comments!

Your Thoughts

  1. Jamie Hill Avatar

    You might be interested in https://github.com/thelucid/glidecss. I open sourced it, after hitting problems like this myself on many occasions.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.