Is there a way to add dark mode to my application with SCSS?

I've been set the task of adding a toggle on an angular web application which will allow users to switch from the default light mode theme to a dark mode theme. I can't find a way to successfully implement this.

When I got the task there was a _variables.scss file in the styles directory. This contained variables for colours, fonts, sizing and spacing. The colours were in maps and then each shade was assigned to a variable using the map-get() method e.g $shade-0: map-get($shades, 'shade-0').

Initially I thought that I could create a themes.scss file and import it alongside _variables.scss. This file would then link to 2 further scss files lightTheme.scss and darkTheme.scss. Each theme file would hold a list of colour variables similar to the original ones in variables.scss. I can get this to work for 1 theme or the other, but I can't switch between theme files.

darkTheme.scss

$shades: (
  'shade-6':                            #f5f5f5,
  'shade-5':                            #BDBDBD,
  'shade-4':                            #9E9E9E,
  'shade-3':                            #757575,
  'shade-2':                            #616161,
  'shade-1':                            #303437,
  'shade-0':                            #404447,
);

$shade-0:                              map-get($shades, 'shade-0');
$shade-1:                              map-get($shades, 'shade-1');
$shade-2:                              map-get($shades, 'shade-2');
$shade-3:                              map-get($shades, 'shade-3');
$shade-4:                              map-get($shades, 'shade-4');
$shade-5:                              map-get($shades, 'shade-5');
$shade-6:                              map-get($shades, 'shade-6');

$colors: (
  'forest':                            #239F28CC,
  'aqua':                              #8ab4f8,
  'ruby':                              #C93939CC,
  'zing':                              #20CAC3CC,
  'carrot':                            #E9853ECC,
  'grape':                             #7542F2CC,
  'midnight':                          #433F5CCC,
  'slate':                             #657786CC,
);

$forest:                               map-get($colors, 'forest');
$aqua:                                 map-get($colors, 'aqua');
$ruby:                                 map-get($colors, 'ruby');
$zing:                                 map-get($colors, 'zing');
$carrot:                               map-get($colors, 'carrot');
$grape:                                map-get($colors, 'grape');
$midnight:                             map-get($colors, 'midnight');
$slate:                                map-get($colors, 'slate');

$bg-color:                            map-get($shades, 'shade-1');
$border-color:                        map-get($shades, 'shade-2');
$border-dark-color:                   map-get($shades, 'shade-3');
$text-color:                          map-get($shades, 'shade-6');
$muted:                               map-get($colors, 'slate');
$subtle:                              map-get($shades, 'shade-4');

lightTheme.scss

$colors: (
      'forest':                            #239F28,
      'aqua':                              #186EEF,
      'ruby':                              #C93939,
      'zing':                              #20CAC3,
      'carrot':                            #E9853E,
      'grape':                             #7542F2,
      'midnight':                          #433F5C,
      'slate':                             #657786,
);
$shades: (
  'shade-0':                            #ffffff,
  'shade-1':                            #f5f5f5,
  'shade-2':                            #d8d8d8,
  'shade-3':                            #bbbbbb,
  'shade-4':                            #979797,
  'shade-5':                            #535353,
  'shade-6':                            #0c0c0c,
);
$shade-0:                              map-get($shades, 'shade-0');
$shade-1:                              map-get($shades, 'shade-1');
$shade-2:                              map-get($shades, 'shade-2');
$shade-3:                              map-get($shades, 'shade-3');
$shade-4:                              map-get($shades, 'shade-4');
$shade-5:                              map-get($shades, 'shade-5');
$shade-6:                              map-get($shades, 'shade-6');
$forest:                               map-get($colors, 'forest');
$aqua:                                 map-get($colors, 'aqua');
$ruby:                                 map-get($colors, 'ruby');
$zing:                                 map-get($colors, 'zing');
$carrot:                               map-get($colors, 'carrot');
$grape:                                map-get($colors, 'grape');
$midnight:                             map-get($colors, 'midnight');
$slate:                                map-get($colors, 'slate');
$bg-color:                             map-get($shades, 'shade-1');
$border-color:                         map-get($shades, 'shade-2');
$border-dark-color:                    map-get($shades, 'shade-3');
$text-color:                           map-get($shades, 'shade-6');
$muted:                                map-get($colors, 'slate');
$subtle:                               map-get($shades, 'shade-4');

themes.scss

@import 'global/lightTheme';
@import 'global/darkTheme';

I did try changing the variables from scss variables to css variables and use them with var() but I ran into difficulties as certain component use darken(), lighten() and mix() and therefore don't compile. Is there a way to get this working?


This question was asked a year ago, but still helpful for anyone reading this, here's a simpler solution.

Highlights

  • Javascript is only used to toggle the class of your root element.
  • You don't have to define separate classes of themes for each element.

All you have to do in your scss file is:

.content {
  padding: 32px;
  @include theme() {
    color: theme-get('text-color');
    background-color: theme-get('bg-color');
  }
}

Implementation

You can make a separate file, let's say themes.scss in which you can define properties for both of your themes:

$themes: (
  darkTheme: (
    'text-color': white,
    'bg-color': #424242
  ),
  lightTheme: (
    'text-color': black,
    'bg-color': #f5f5f5
  )
);

Use a mixin:

// From Sass 2.0 on, it is no longer allowed to declare globals on the fly.
$theme-map: null;

@mixin theme() {
  @each $theme, $map in $themes {
    // $theme: darkTheme, lightTheme
    // $map: ('text-color': ..., 'bg-color': ...)

    // make the $map globally accessible, so that theme-get() can access it
    $theme-map: $map !global;

    // make a class for each theme using interpolation -> #{}
    // use & for making the theme class ancestor of the class
    // from which you use @include theme() {...}
    .#{$theme} & {
      @content;    // the content inside @include theme() {...}
    }
  }
  // no use of the variable $theme-map now
  $theme-map: null !global;
}

Now, you can access the property of a theme using map-get($theme-map, ...). But we can avoid passing $theme-map as an argument every time, by defining a function which will do it for us.

@function theme-get($key) {
  @return map-get($theme-map, $key);
}

The resultant css file will be:

.content {
  padding: 32px;
}

.darkTheme .content {
  color: white;
  background-color: #424242;
}

.lightTheme .content {
  color: black;
  background-color: #f5f5f5;
}

Example

Here is a fiddle for demonstration: https://jsfiddle.net/aksh101099/zubnapr9/1/


I prepared a CodePen to demonstrate theme switching with CSS variables.

I define the color variables depending on the app container's class (.light or .dark). Simply toggling those classes will then change the site's theme.

Bear in mind, that CSS variables are not fully supported in all browsers (94% globally).

Read more about CSS variables.


I found this article on Medium so I think you can check it out

The idea is you query a body tag in your html then you set the class for it