Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Switching themes using Less + CSS Modules in React

In my project, I use CSS Modules with Less, which means I get the best of both worlds.

My src folder looks something like this:

components/
    [all components]
theme/
    themes/
        lightTheme.less
        darkTheme.less
    palette.less

palette.less:

@import './themes/lightTheme.less';

Then, in every component that wants to use colors from the theme, I do:

component.module.less:

@import '../../theme/palette.less';

.element {
    background-color: @primary;
}

This structure lets me edit palette.less to import the theme I want to use. The thing is that I want to let the users choose their preferred theme on their own. Themes should be switchable on runtime, which means I somehow need to have both themes compiled.


I imagine the perfect solution to be something like this:

app.less

body {
    @theme: @light-theme;

    &.dark-theme {
        @theme: @dark-theme;
    }
}

And then somehow import this @theme variable in every component and read properties from it (i.e. @theme[primary]).

Unfortunately, Less variables scoping don't work like this.


I am open-minded to any solution that uses Less modules.

Thank you!

like image 662
Itay Ganor Avatar asked Jan 17 '20 12:01

Itay Ganor


People also ask

Should I use CSS modules in React?

CSS Modules is popular in React because of the following reasons: Scoped: CSS Modules are scoped when you use them the right way. Highly composable: You can compose different styles in a lot of ways. Tree shakable: Styles you don't use are removed, just like modern JS tooling.

Can I import multiple CSS files React?

You can import multiple css modules into a component or function using Object. assign For example if you import a button css modules to your Demo component, add this to the components default styles. Then in your component... start using pure css styles.


1 Answers

I know that you've probably looking for a solution that uses Less / CSS modules, but it's very likely that your situation can be solved solely with the use of css variables (as Morpheus commented on your question).

How it would work?

You'd have to ensure all your styling does not use hardcoded values, i.e. instead of:

.awesome-div {
  background-color: #fefefe;
}

You would have:

:root {
  --awesome-color: #fefefe;
}

.awesome-div {
  background-color: var(--awesome-color);
}

Changing between light and dark

There are two ways of changing themes in this approach:

  • Use vanilla Js code within React to update the :root CSS element, check this codepen for more information;
  • Just load a component containing all new :root variables in its component.css file;

In React (in vanilla CSS too) you can easily have multiple components/elements declaring their own :root at their .css files.

Furthermore, any new :root will overwrite conflicting values from previous :root. For example if at file app.css we have :root { --color: red; } and, when loading another component, component A for instance, where in component_a.css we have the same variable overwritten, e.g. :root { --color: blue; } the one rendered in our browsers will be the one from component A.

Following this logic, you can have a dummy component that does and renders exactly nothing, but instead in this component.js file you import the .css of a theme, e.g.:

import './light.css'; // suppose this is the light-theme dummy component

When switching themes in your app you'd just have to remove the dummy component from scene and call the other one.

I'm not too experienced with codepen to the point of providing you an example containing imports/modules over there, but I hope the above explanation can give you an idea of what I mean. Still, here's a brief pseudo-code of what I'm intending to demonstrate:


loadTheme() {
  if (this.state.theme === 'dark') return <LightTheme />;
  if (this.state.theme === 'user-3232') return <UserTheme />;
  return <DarkTheme />;
}

render() {
  return (
    <App>
      {this.loadTheme()}
      <OtherContent>
    </App>
  );
}

like image 196
LuDanin Avatar answered Sep 27 '22 19:09

LuDanin