Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement switchable themes in scss?

Tags:

css

sass

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..?

like image 487
thebjorn Avatar asked Oct 16 '20 13:10

thebjorn


People also ask

Can SCSS variable change dynamically?

We can dynamically update SCSS variables using ReactJS with the help of a project by achieving theme switching of the card component between light and dark theme.

How do I change colors in SCSS?

Sass Set Color Functions An RGB color value is specified with: rgb(red, green, blue). Each parameter defines the intensity of that color and can be an integer between 0 and 255, or a percentage value (from 0% to 100%). Sets a color using the Red-Green-Blue-Alpha (RGBA) model.

How do I add a style to SCSS?

The structure of SCSS rules First, choose one or more elements using IDs, classes, or other CSS selectors. Then, add styles. In this example, we select the elements with button class and add some properties. This is valid as CSS code as well as SCSS code.

Is Sass and SCSS same?

SASS (Syntactically Awesome Style Sheets) is a pre-processor scripting language that will be compiled or interpreted into CSS. SassScript is itself a scripting language whereas SCSS is the main syntax for the SASS which builds on top of the existing CSS syntax.


2 Answers

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.

like image 58
Amar Syla Avatar answered Oct 23 '22 21:10

Amar Syla


All credit goes to Dmitry Borody

I would recommend an approach like what is mentioned in this Medium article. With this approach, you can assign what classes need to be themed without specifically mentioning the theme name so multiple themes can be applied at once.

First, you set up a SASS map containing your themes. The keys can be whatever makes sense to you, just make sure that each theme is using the same name for the same thing.

$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,
  ),
);

Then use the mixin and function pair to add theme support.

body {
  background-color: white;
  @include themify {
    background-color: theme( 'backgroundColor' );
  }
}

.button {
  background-color: lightgray;
  color: black;

  @include themify {
    background-color: theme( 'buttonBackgrounColor' );
    color: theme( 'buttonTextColor' );
  }

  &:focus,
  &:hover {
    background-color: gray;
    
    @include themify {
      background-color: theme( 'buttonBackgroundHoverColor' );
      color: theme( 'buttonTextHoverColor' );
    }
  }
}

If you're going to be adding a lot of themes or a theme will be touching a lot of stuff, you might want to set up your SCSS files a little differently so that all the theming doesn't bloat your main CSS file (like the example above would do). One way to do this might be to create a themes.scss file and replicate any selector paths that need theming and have a second build script that outputs just the themes.scss file.

The Mixin

@mixin themify( $themes: $themes ) {
  @each $theme, $map in $themes {

    .theme-#{$theme} & {
      $theme-map: () !global;
      @each $key, $submap in $map {
        $value: map-get(map-get($themes, $theme), '#{$key}');
        $theme-map: map-merge($theme-map, ($key: $value)) !global;
      }

      @content;
      $theme-map: null !global;
    }

  }
}

The Function

@function themed( $key ) {
  @return map-get( $theme-map, $key );
}
like image 21
hungerstar Avatar answered Oct 23 '22 23:10

hungerstar