CSS modules are great, they encapsulate component styles to themselves: so that we could start using
.container for everything in everywhere again 😆
Not too long ago
create-react-app released version 2 with the feature of using CSS modules. I'm sure once people upgrade to
react-scripts@2, they would immediately opt-in with excitement and start using CSS modules without a doubt: it makes CSS modular like everything else!
But sooner or later, you would realize there is one thing we cannot easily do anymore in CSS modules – a rather important (if not fundamental) thing in CSS: selecting and overwriting the styles of the (deep nested) child component that’s in a different module from the parent.
Before we come back to react, let’s take a look at how Angular approaches the problem. In Angular, style encapsulation is actually done in a different way with emulated shadow DOM. The actual implementation is through adding extra generated attributes to the DOM elements (rather than changing the class names, which will be mentioned below).
The way to target the child components is simple: you would just need to use
>>>) before the children selectors. And it would generate the styles for that element without any attribute attached, thus achieving the goal to target any nested child elements and get around the view encapsulation.
With Vue, I haven’t work with it that much, but I saw this and assumed it is pretty similar to Angular in terms of implementation.
But in CSS modules, the actual implementation is to hash and rename the class names to make sure they are unique.
That made my first trial to do things in a way that’s similar to Angular failed. My first intuitive way to do it is to use the
: global keyword to un-encapsulate (or de-encapsulate) the child selectors, but that didn't work as the children's selectors are hashed and renamed, which can't be easily targeted in this way.
Then I talked to @alemesa and found out @donghyukjacobjang and he are doing each component with an un-scoped normal string class name which has the sole purpose of being targeted from outside of the CSS module. This way has been working for them quite well, but in my opinion, this way is more like a convention that people have to follow; and it somewhat defeats the purpose and benefits of using CSS modules.
After doing some searches, I still couldn’t find anything that’s quite similar to how Angular and Vue do it. But I did find an interesting solution here that could satisfy me and my needs. It suggests that we could define a child and its styles in a parent module first, and then import the child class name and pass that down to the child as one of the props in JSX. This solution, in my opinion, is still kind of a way by convention as the children would need to know to expect and use the class names from the props. But it’s the best solution I could find/think of at the moment, and it also provides more predictability and stability compared to the Angular/Vue way.
Although at the moment if you ask me, I would still prefer the shadow DOM implementation and emulation with HTML element attributes like the way in Angular, CSS modules are great too! It’s very easy to opt-in (thanks to CRA too!), and also you could migrate to it gradually and starting enjoying its benefit today.
All I have to say is that with all the benefits, it also comes with some minor issues that you need to consider before jumping into it, and the problem we discussed here is one of them. Moreover, I’d like to also point out a few other things I noticed for your consideration:
- It is recommended that you use
camelCasefor the class names. (You could use
kabab-case, but you would not want to.)
- The generated class names (with the CRA setup) are not uglified and usually very long (long enough to increase the bundle size).
Please leave a comment to share your opinions and solutions to this problem, cheers!