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

5

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?

Probst answered 13/7, 2019 at 9:39 Comment(2)
you can import styles with conditions, please check this link : #36368032Microcrystalline
Do remember to consider the user's preset, check the prefers-color-scheme.Compendious
D
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.

Deflected answered 13/7, 2019 at 11:53 Comment(4)
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?Probst
Yes, define the darker colors the same way as all other colors, and use them later! I'll update the pen with an example.Deflected
@BenNewton did you see the update I made in the pen?Deflected
yes I did. Thanks so much for your help, it worked a treat!Probst
K
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/

Khartoum answered 2/8, 2020 at 23:11 Comment(2)
This a wonderful solution! Works like a charm, thank you for that!Aframe
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 goMicrocrystalline
D
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.

Deflected answered 13/7, 2019 at 11:53 Comment(4)
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?Probst
Yes, define the darker colors the same way as all other colors, and use them later! I'll update the pen with an example.Deflected
@BenNewton did you see the update I made in the pen?Deflected
yes I did. Thanks so much for your help, it worked a treat!Probst
F
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

Ferro answered 13/7, 2019 at 14:20 Comment(0)
S
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;
}
Sita answered 21/11, 2021 at 6:54 Comment(0)
P
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,
      ),
    );

Pipette answered 12/10, 2022 at 7:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.