Right now we are using a scss mixin ( https://github.com/zellwk/themify ) to provide different themes for our react components in our app.
It works great, but produces rather long selector chains in the compiled css file.
While we do have to provide the ability to change themes during runtime ( which is no hassle with the current solution, we only have to switch one classname on the body element ) the default usecase is, that the theme does not change.
So we thought about reducing the complexity and filesize of our stylesheets by splitting them up in separate files.
Example:
themes.scss:
$themes: (
red: (
mainColor: #aa3939,
secondaryColor: #D46A6A
),
blue: (
mainColor: #2e4272,
secondaryColor: #4f628e
)
);
button.sccs
@import 'themes.scss';
.button {
@include themify($themes) {
color: themed('secondaryColor');
background-color: themed('mainColor');
}}
}
This becomes:
.theme-red .button {
color: #D46A6A;
background-color: #aa3939;
}
.theme-blue .button {
color: #4f628e;
background-color: #2e4272;
}
Now I want this to become:
theme-red.css:
.button {
color: #D46A6A;
background-color: #aa3939;
}
theme-blue.css:
.button {
color: #4f628e;
background-color: #2e4272;
}
We are not depenent on the themify mixin, we could change that to any kind of solution one could make work with webpack. Any hint in the right direction would be appreciated! :)
CSS Custom Properties (CSS Variables) && postcss-custom-properties
Yeap, CSS Custom Properties will the perfect solution here.
Also, you can write PostCSS plugin, which will replace #D46A6A → #4f628e (but it is not so explicit and maintainable).
@ai @michael-ciniawsky
thanks, that looks promising! but how would I generate separate files for each theme?
@manulitopetito you need to run PostCSS manually (not a big deal, just simple Node.js script). Here is API: JS API.
Just run postcss().process() for each theme and save result to files.
@manulitopetito Since you intend to use webpack, that will be difficult to do (CSS File per JS Entry instead of CSS File per e.g Theme), but you don't really need n separate files since you can simply change the Custom Properties via JS during runtime. They are dynamic
:root {
--theme-color: blue;
--theme-bg-color: red;
}
.some__class {
color: var(--theme-color);
background-color: var(--theme-bg-color);
}
// document.documentElement === :root {}
const theme = (prop, value) => document.documentElement.style.setProperty(prop, value);
const button = document.getElementById('button')
button.addEventListener('click', function () {
// Will update .some__class { color: var(--theme-color); } to be 'green'
theme('--theme-color', 'green')
})
https://smashingmagazine.com/2017/04/start-using-css-custom-properties/
https://csswizardry.com/2016/10/pragmatic-practical-progressive-theming-with-custom-properties/
thanks @michael-ciniawsky
sorry if I wasn't clear.
I want one generated css file per theme.
like:
components/button/button.scss,
components/button/input.scss,
styles/main.scss
all get compiled to theme-light.css, theme-dark.css with different color variables applied.
We can't use custom properties natively because we have to support IE 10 / 11
so basically all webpack should do is compile all scss files to one theme-[x].css file where the custom-properties got replaced with the theme values.
so basically all webpack should do is compile all scss files to one theme-[x].css file where the custom-properties got replaced with the theme values.
@manulitopetito Since you intend to use webpack, that will be difficult to do (CSS File per JS Entry instead of CSS File per e.g Theme)
☝️ Consider a separate build step for CSS then (e.g with gulp)
or something like styled-components/emotion/[ Insert CSS-in-JS library here...]
The multiple theme files with SASS approach won't work with webpack
We can't use custom properties natively because we have to support IE 10 / 11
Maybe you can use a polyfill like https://github.com/jhildenbiddle/css-vars-ponyfill in this case. It appears to support IE 9+
I'm dealing with the similar problem currently, I have to support IE and can't use custom properties polyfill. Then I write a webpack plugin themes-switch to do that. Maybe it can help you.
Now I use variables to define different colors and dimensions, then webpack would generate multiple theme files at once, and the style formats can be less, postcss and sass. Then I can make a realtime-switch in my app.
theme-a.less
@color-main: #0A6EFA;
@color-text: #000;
theme-b.less
@color-main: #222A42;
@color-text: #FFF;
default.less
@import 'theme-a.less';
main.less
@import 'default.less';
.main {
background: @color-main;
}
Config in webpack
new ThemesGeneratorPlugin({
srcDir: 'src',
themesDir: 'src/assets/themes',
outputDir: 'static/css',
defaultStyleName: 'default.less',
themesLoader: {
test: /\.(less|css)$/,
loaders: [
{ loader: require.resolve('css-loader') },
{ loader: require.resolve('less-loader') }
]
}
})
In use
changeTheme('theme-b', 'theme-b-123456.css')
@manulitopetito I tried to use the similar approach with the help of these two tutorials and faced the same issue as you are facing.
Sass Theming: The Neverending Story
The CSS is generated in a single file instead of two separate files. I wanted to generate two separate CSS files just like you. I did some R&D about the issue and finally figured out how to do this.
First of all you need to break your themes.scss file into two separate files as shown below.
$themes: (
red: (
mainColor: #aa3939,
secondaryColor: #D46A6A
)
);
$themes: (
red: (
mainColor: #2e4272,
secondaryColor: #4f628e
)
);
Next you need to make some changes in your button.scss file. Simply remove the @import statement from the top because we will import theme specific variables into their own separate files as shown below
.button {
@include themify($themes) {
color: themed('secondaryColor');
background-color: themed('mainColor');
}
}
Next you need to create two separate theme files. In these files, you need to import theme specific variables file, mixin file and your button.scss file
// Import red theme variables file
@import 'theme-red-variables';
// Import mixins file, where you have defined themify and themed mixins
@import 'mixins';
// Import button.scss file in this theme file
@import 'button';
// Import blue theme variables file
@import 'theme-blue-variables';
// Import mixins file, where you have defined themify and themed mixins
@import 'mixins';
// Import button.scss file in this theme file
@import 'button';
Two separate files will be generated using the above technique
.button {
color: #D46A6A;
background-color: #aa3939;
}
.button {
color: #4f628e;
background-color: #2e4272;
}
I hope I have explained the solution well and it will help you to resolve your problem.
@waqasdotnet Hi, I think your idea is a great way, but I have not tried it successfully. Can you share an example in Codesandbox? Thanks a lot !
Most helpful comment
I'm dealing with the similar problem currently, I have to support IE and can't use custom properties polyfill. Then I write a webpack plugin themes-switch to do that. Maybe it can help you.
Now I use variables to define different colors and dimensions, then webpack would generate multiple theme files at once, and the style formats can be less, postcss and sass. Then I can make a realtime-switch in my app.
theme-a.less
theme-b.less
default.less
main.less
Config in webpack
In use