Selecting elements in children components within CSS modules
Tags: css, webdev, react
Published: 2019-02-21 17:00:02

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!

## Inevitable problem

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.

## How Angular and Vue solves the problem

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](https://angular.io/guide/component-styles#inspecting-generated-css) 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 [`::ng-deep` (or `/deep/`/`>>>`)](https://angular.io/guide/component-styles#deprecated-deep--and-ng-deep) 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](https://vue-loader.vuejs.org/guide/scoped-css.html) and assumed it is pretty similar to Angular in terms of implementation.

## How we could do it in React with CRA

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](http://engineering.liefery.com/2018/06/27/overriding-styles-with-CSS-modules.html) 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.

## Conclusion

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 `camelCase` for 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!