How to Implement Theme Switching with Sass

Implement Theme Switching with Sass Overview

There are several different schemes to achieve theme switching, such as using css variables, using JavaScript to dynamically load the corresponding theme style files, etc. This article mainly talks about how to use Sass to achieve theme switching.

As a CSS preprocessor, Sass needs to be compiled into CSS before it can be recognized and parsed by browsers. Therefore, it is not possible to use Sass directly in the browser to achieve dynamic switching similar to CSS variables. Essentially, if there are several themes in the project, it is necessary to define several theme styles in advance and introduce them all.

1. First, we need to add a theme logo to the top-level element of the application to identify the current theme and use it for the corresponding theme style in the subsequent application. The identifier can be a data attribute, a class, or an id, and the data attribute is used here.

<html>
  <div class="app" >"light"></div>
</html>

2. Then, every time the theme is switched, by updating the flag, the page will apply the corresponding theme style defined in advance in the style file.

.app {
  &[>'light'] {
    color: #333;
  }
  
  &[>'dark'] {
    color: #fff;
  }
}

How to Use Sass to Achieve Simple Theme Switching

Based on the nature and idea of ​​theme switching, we can implement a simple theme switching by hardcoding.

<div id="app" class="app">
  <h1 class="title">Hello, World</h1>
  <p class="subtitle">Current Theme:<span id="theme-current">Light</span></p>
  <button class="theme-switch light" >"light">Light</button>
  <button class="theme-switch dark" >"dark">Dark</button>
</div>

First add a theme logo to the application, here I add a data attribute > light to the body element.

<body >"light">
  <div class="app"></div>
</body>

Then, all theme styles are defined in advance.

// All theme styles
$bg-color-light: #ffffff;
$bg-color-dark: #091a28;
$title-color-light: #363636;
$title-color-dark: #ffffff;
$subtitle-color-light: #4a4a4a;
$subtitle-color-dark: cyan;

.app {
  // Default theme styles (light theme)
  background-color: $bg-color-light;
  
  // Dark theme styles
  [theme='dark'] & {
    background-color: $bg-color-dark;
  }
}

.title {
  color: $title-color-light;
  
  [theme='dark'] & {
    color: $title-color-dark;
  }
}

.subtitle {
  color: $subtitle-color-light;
  
  [theme='dark'] & {
    color: $subtitle-color-dark;
  }
}

Finally, when we click on a different theme button, the theme ID on the body > full code is updated.

However, the implementation is a bit rough, and there are two small problems:

1. In each CSS selector that needs to apply the theme style, it is tedious to write the style required by the corresponding theme.

2. If there are multiple themes, the amount of code will increase extremely, and many are duplicate "template code "template code".

In response to these problems, we can use some features of Sass to implement an advanced version of theme switching.

How to Use Sass to Achieve Advanced Theme Switching

First of all, for the problems exposed by the basic version. We need to make a small tweak to the Sass variable. Here we encapsulate the theme style into a map format, and each element in the map corresponds to the style under different themes.

// All theme styles
$bg-color: (
  // Light
  light: #fff,
  // Dark
  dark: #091a28
);

$title-color: (
  light: #363636,
  dark: #ffffff
);

$subtitle-color: (
  light: #4a4a4a,
  dark: cyan
);

For the problem of repeated template code and cumbersome code, there is a feature mixin in Sass, which can be used.

Next, we have to encapsulate a mixin to solve the tedious problem of basic version 1-handwritten code. The interpolation expression #{} in Sass and the map-get method are used here. #{} is similar to computed properties in JavaScript, and the property name can be dynamically set. The map-get method is used to get the value corresponding to a property from the map.

@mixin themify($key, $valueMap) {
  // Default theme
  #{$key}: map-get($valueMap, 'light');
    
  // Dark theme
  [theme='dark'] & {
    #{$key}: map-get($valueMap, 'dark');
  }
}

"themify" mainly encapsulates the default theme style light, and the dark theme style. In the selector, we only need to include these styles.

.app {
  @include themify('background-color', $bg-color);
}

.title {
  @include themify('color', $title-color);
}

.subtitle {
  @include themify('color', $subtitle-color);
}

Is the code more concise now? Save yourself the tedious template code by hand! For the problem of "multi-theme template code will be more", it is very easy to solve. Just simply modify the mixin and add the corresponding theme style.

@mixin themify($key, $valueMap) {
  // Light theme
  #{$key}: map-get($valueMap, 'light');
    
  // Dark theme
  [theme='dark'] & {
    #{$key}: map-get($valueMap, 'dark');
  }
  
  // Dark1 theme
  [theme='dark1'] & {
    #{$key}: map-get($valueMap, 'dark1');
  }
  
  // Dark2 theme
  [theme='dark2'] & {
    #{$key}: map-get($valueMap, 'dark2');
  }
}

Of course, we can also optimize the mixin and encapsulate the theme into a list format. Then simplify the mixin by iterating over the topics.

@mixin themify($key, $valueMap) {
  // Theme list
  $themes: light, dark;
  
  @each $theme in $themes {
    [theme=#{$theme}] & {
      #{$key}: map-get($valueMap, $theme);
    }
  }
}

This looks so much cooler!

As a popular CSS preprocessor, Sass provides features such as interpolation expression #{} and map type, which provides a lot of convenience in implementing theme switching. Of course, there are still many points that can be optimized for Sass to achieve theme switching.

For example, if there are multiple theme styles to be applied, @include must be written for each of them. This is a bit of a hassle. Can we just write @include once?

.app {
  @include themify('background-color', $bg-color);
  @include themify('color', $text-color);
  // ...
}

// Write @include once
.app {
  @include themify(
    (
    'background-color': $bg-color,
    'color': $text-color
    )
  );
}

If you also need to use !important to override some styles that cannot be applied due to weight issues. For example, if an external UI library is used, and !important is used in the external UI library, this style needs to be overridden. How to deal with it?

// Here is an idea. You can add a parameter $important.
@mixin themify($key, $valueMap: null, $important: false) {
    // xxx
}


Leave a reply



Submit