How I Theme My React App With Sass
Before any coding, I’ve been always starting my projects with a clear design — or at least a rough prototype — in mind. Probably my biggest and still ongoing diet management application is no exception from this.
However, this time, when I finished the designs for the app, I felt like something was missing. I started with a light, white-based design that resembled the sleek design of macOS but I was always a fan of dark themes.
So I started to experiment with different styles. Eventually, I created two separate designs; a light and a dark theme. I really liked the dark theme. It helps both the longevity of my screen as well as my eyes in dark conditions. But since I originally started with the white design, it felt like it is as much as part of the application as the dark one. I decided; why not keep both and create a theme chooser?
It works on a simple principle: depending on a class on the html
tag, we style elements differently.
.theme-light body { background: #FFF; }
.theme-dark body { background: #000; }
But how do we prevent to write out the same styles for every theme? This is where mixins will come into place. But first, let’s start by creating the color palette for them.
Setting Up The Color Palette
The color palette will hold every color for each theme. To separate them from my main styles, I’ve created a partial, called _color-palette.scss
, and pulled it inside my main sass file. Inside it, we will have a $themes
variable which will hold a list of map: a map for each theme.
$themes: (
light: (
color-background: #FAFAFA,
color-card: #FFF
.
.
.
),
dark: (
color-background: #37474F,
color-card: #212121
.
.
.
)
);
Maps in Sass are a powerful way to hold key-value pairs. We can easily look up values by its corresponding key and we can get a value by using the built-in map-get
function. For example, to get the background color for the light theme, we would have to use two map-get
functions in conjunction:
// Get value from nested maps
map-get(map-get($themes, 'light'), 'color-background');
First, we get the map for the light theme, then we wrap everything inside another map-get
to get the specific key, which in this case is the color-background
variable.
Now, this is no dynamic by any means. It would be insane to write this out to change the color of a UI element not to mention this only covers one theme. To easily grab a color no matter what theme is active, we can create a handy mixin for it.
Making The Mixin
This will be the core handler that will generate styles for each theme we have. In order to know how to create the mixin, let’s see what we would like to have in the end:
// Get the color-background property from the active theme map and apply its value to the "background" css property
body {
@include theme-aware('background', 'color-background');
}
We want to have a mixin called theme-aware
to which we can pass two values:
- One for the CSS property that we would like to change
- One for the name of the key inside our
color-palette
In the example above, it should result in the following: background: #FAFAFA;
or background: #37474F
. But how do we know which one to apply? And what if we want the change to happen seamlessly without a page reload?
We don’t really have the power to switch styles inside CSS, so instead, we can create a separate rule for each theme. Say in case the html
tag has a .theme-light
class, we apply the styles corresponding to the light theme. If we have a .theme-dark
class, we apply the ones associated with that theme.
With this in mind, we can create the mixin:
/**
* theme-aware - Change color of a css property based on the currently active theme
*
* @param {key} CSS property
* @param {color} Color name defined in the themes under _color-palette.scss
*
* @example - @include theme-aware('background', 'color-background');
* @returns - background: #FFF;
*/
@mixin theme-aware($key, $color) {
@each $theme-name, $theme-color in $themes {
.theme-#{$theme-name} & {
#{$key}: map-get(map-get($themes, $theme-name), $color)
}
}
}
It accepts a $key
and a $color
. Inside the mixin, we loop through each theme using the @each
control and for each theme, we add the following rule:
.theme-<name of the theme> & { ... }
// So in the end we will have
.theme-light & { background: #FAFAFA; }
.theme-dark & { background: #37474F; }
Inside the rule, we can then use the passed $key
with interpolation and the two map-get
functions to get the color from the theme.
Now if we use the mixin, it will generate two different rules for the two themes:
body {
@include theme-aware('background', 'color-background');
}
// It will generate the following rules:
body {
.theme-light & { background: #FAFAFA; }
.theme-dark & { background: #37474F; }
}
// After compiling scss down to css, we get the following
.theme-light body {
background: #FAFAFA;
}
.theme-dark body {
background: #37474F;
}
We can leverage the element &
selector in SCSS to apply styles to the element if it is inside a parent which matches the theme selector.
Pulling Everything Into React
Now that we have everything, all we need to do is switch a class on the body and that will instantly change the whole theme of the application:
To put this into React I’m using a select
which updates the user preferences as well as switches class on the html
.
The UI
I defined the available themes in an array and loop through it to create an option for each. The component is coming from the Ant library and whenever the value changes, it calls the changeTheme
function which is responsible for switching themes.
const themes = [
'light',
'dark'
];
<Select defaultValue={user.theme} onChange={changeTheme}>
{themes.map((theme, index) =>
<Option value={theme} key={index}>{theme}</Option>
)}
</Select>
The controller
The function gets the value of the selected option and uses it to update both the user’s default theme and the class on the HTML element:
changeTheme = (theme) => {
user.theme = theme;
document.documentElement.className = '';
document.documentElement.classList.add(`theme-${user.theme}`);
alert('Theme updated...');
}
The matter of fact is that it can be done with anything not just React. The key part here is leveraging sass functionality such as maps and mixins for generating the styles for each theme.
Summary
With this, you can create as many themes as you want. All you need to do is create a color palette for a new theme and use the theme-aware mixin for the elements that you want to style. You add a new option for the theme into the select
and you’re all set. 🎨
Thank you for taking time to read this article, happy styling!
Want to take your CSS to the next level? Learn how to improve its the performance and scalability:
Rocket Launch Your Career
Speed up your learning progress with our mentorship program. Join as a mentee to unlock the full potential of Webtips and get a personalized learning experience by experts to master the following frontend technologies: