9

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?

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
Ben Newton
  • 95
  • 1
  • 1
  • 6
  • you can import styles with conditions, please check this link : https://stackoverflow.com/questions/36367532/how-can-i-conditionally-import-an-es6-module – Alex Jul 13 '19 at 10:26
  • 1
    Do remember to consider the user's preset, check the [`prefers-color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme). – David Thomas Jul 13 '19 at 14:19

5 Answers5

16

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/2/

Akshdeep Singh
  • 1,301
  • 1
  • 19
  • 34
  • 2
    This a wonderful solution! Works like a charm, thank you for that! – kmnowak Oct 16 '20 at 18:56
  • isn't it creating double the css? (so if you have 10 themes then 10x the css). While the solutions itself works fine, in some cases sticking to css variables i.e. `:root { --sth: xxx}` and `var(--sth)` si the way to go – Alex Dec 26 '21 at 12:02
6

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.

Barthy
  • 3,151
  • 1
  • 25
  • 42
  • This is really great @Barthy, it's near enough what I'm after. This will solve the majority of my issues except there are certain components in my application where one of the color is made darker, e.g `background-color: darken($bg-color, 8%);` . Similarly, `lighten()` and `mix()` are also used. I know CSS variables and SCSS don't play well together, do you have any suggetions? – Ben Newton Jul 13 '19 at 21:16
  • Yes, define the darker colors the same way as all other colors, and use them later! I'll update the pen with an example. – Barthy Jul 14 '19 at 13:33
  • @BenNewton did you see the update I made in the [pen](https://codepen.io/BarthyB/pen/EBzxje)? – Barthy Jul 14 '19 at 23:08
  • yes I did. Thanks so much for your help, it worked a treat! – Ben Newton Jul 15 '19 at 14:07
1

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

Tony Ngo
  • 19,166
  • 4
  • 38
  • 60
1

To go along with Akshdeep Singh's answer if you are using css (or scss) modules you are going to need to define your theme keyword in your selector to be global so that it doesn't get the module naming applied to it:

@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() {...}
      :global(.#{$theme}) & {
        @content;    // the content inside @include theme() {...}
      }
    }
    // no use of the variable $theme-map now
    $theme-map: null !global;
}
Leyla Becker
  • 58
  • 1
  • 5
0

Yes. Its possible to achieve dark/light theme in SASS/SCSS using themify. I already used in my project and its working fine Please check below reference link: medium article

HTML:

    <main id="app-root" class="theme-dark">
          ...
    </main>
    
    <main id="app-root" class="theme-light">
      ...
    </main>

Define theme:

    $themes: (
      light: (
        backgroundColor: #fff,
        textColor: #408bbd,
        buttonTextColor: #408bbd,
        buttonTextTransform: none,
        buttonTextHoverColor: #61b0e7,
        buttonColor: #fff,
        buttonBorder: 2px solid #fff,
      ),
      dark: (
        backgroundColor: #222,
        textColor: #ddd,
        buttonTextColor: #aaa,
        buttonTextTransform: uppercase,
        buttonTextHoverColor: #ddd,
        buttonColor: #333,
        buttonBorder: 1px solid #aaa,
      ),
    );

pmatatias
  • 3,491
  • 3
  • 10
  • 30