Search code examples
csssass

How to implement switchable themes in scss?


I have an existing project with a scss file that uses semantic variables:

$background-color: white;

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

I would like to change the background to black when I add a theming class to the body:

<body class="theme-dark">...</body>

and back to white if I remove the class (or switch to a theme-light).

I haven't found any light-weight methods to do this in scss (parametrizing a class for each theme seems like a very hard to maintain approach).

I've found a hybrid scss/css-custom properties solution:

original:

.theme-light {
    --background-color: white;
}

update (based on Amar's answer):

:root {
    --background-color: white;
}
.theme-dark {
    --background-color: black;
}

$background-color: var(--background-color);
body {
    background-color: $background-color;
}

Defining the scss variable as having a css-variable expansion as the value, i.e. (from above):

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

generates the following css:

:root { --background-color: white; }
.theme-dark { --background-color: black; }
body { background-color: var(--background-color); }

which seems to be what we want...?

I like it since it only requires changing the definition of $background-color (not every usage in a very large scss file), but I'm unsure if this is a reasonable solution? I'm pretty new to scss, so maybe I've missed some feature..?


Solution

  • Doing this with SCSS is possible but you would have to add styles to all elements you want to theme. That is because SCSS is compiled at build-time and you can't toggle the variables with classes. An example would be:

    $background-color-white: white;
    $background-color-black: black;
    
    body {
      background-color: $background-color-white;
    }
    
    .something-else {
      background-color: $background-color-white;
    }
    
    // Dark theme
    body.theme-dark {
      background-color: $background-color-black;
    
      .something-else {
        background-color: $background-color-black;
      }
    }
    

    The best way to currently do it is by using CSS variables. You would define the default variables like this:

    :root {
      --background-color: white;
      --text-color: black;
    }
    
    .theme-dark {
      --background-color: black;
      --text-color: white;
    }
    

    Then, you would use these variables in your elements like this:

    body {
      background-color: var(--background-color);
      color: var(--text-color);
    }
    

    If the body element has the theme-dark class, it will use the variables defined for that class. Otherwise, it will use the default root variables.