Reputation: 27311
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..?
Upvotes: 18
Views: 26827
Reputation: 1
Add class="theme-light" to any root tag. Here we will toggle current theme with replacing class from "theme-light" to "theme-dark", for example.
<html class="theme-light">
Of course, "light" and "dark" you can replace with your theme name. Don't forget update $themes
Now we need to add two scss files. First,
themes.scss
$themes: (
light: (
primary: #fff,
),
dark: (
primary: #212121,
),
);
Second,
themed-style.scss
@import './themes';
@mixin themed-style($key, $color) {
@each $theme-name, $theme-color in $themes {
:global(.theme-#{$theme-name}) & {
#{$key}: map-get(map-get($themes, $theme-name), $color);
}
}
}
Finally, use it
@import './themed-style';
.root {
@include themed-style('background', 'primary');
}
Upvotes: 0
Reputation: 3653
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.
Upvotes: 15
Reputation: 21685
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 );
}
Upvotes: 11