Styling Components with CSS Modules
CSS has been the Achilles heel of modern component-based web development since its invent, and has continued to be a difficult situation. Fundamentally components have different lifecycles than the applications they are used, and often used in multiple applications needing to be styled in response to their demands of the application. So the questions of where to expose configuration and providing some level encapsulation, have been core issue to resolve. While React and friends have done a reasonable job of making components reusable, CSS has lagged behind. At the highest levels combining styling with a component makes sense, but this is not something for which CSS was designed. As a result multiple solutions have come about like BEM (Block Element Modifier) and inline styling tools.
While BEM ostensibly provides a way to namespace CSS, it is entirely manual and requires a large amount of mental overhead, while at the same time offers few protections from inadvertent mistakes (super easy to do bad things).The proponents of BEM claim this is a good thing, we’ll have to disagree.
There are also inline (or inline like) styling tools like JSS, Glamour, Styled Components and a host of other solutions, however they tend towards the baby bathwater situation, that is inlining css or css creation, preventing any overrides, and breaking any css conventions and tooling to style components. These solutions also tend to provide little in the way of migrating existing code or knowledge. All of these characteristics, together, make things harder than they need to be as all overrides have to be carefully planned and communicated during the creation of the component.
Finally there are CSS Modules. CSS Modules automatically namespace CSS class names and expose an object with the keys that correspond to the compiled class names in the CSS file. CSS Modules contain real CSS; as such they are significantly easier to migrate to, than the aforementioned alternatives, and they maintain most of the tooling such as preprocessors, linters, auto-prefixers, inspectors and infrastructure that may already exist in a project.
To use CSS Modules, you write CSS and the compiler exposes the class’s as key value pairs on an object.
For example if the CSS looks like:
It would expose a module like:
The crazy looking CSS keys provide name-spacing so elements have accidental styling. Thus a CSS Module can then be used with a component like so:
This works well, but because CSS Modules namespace the CSS (again a good thing), the namespaces themselves are very hard to override; though sometimes you really do want to; think theme’ing,. Overrides are particularly important for components where each usage may require significant divergence from the original look.
To address this problem with CSS Modules, I wrote a tiny util called Emeth, (less than 100 lines of code, including whitespace and comments, and no dependencies). Emeth essentially keeps a map between component names (displayName) and the corresponding css. Decoupling the component from original style module.
As a result you can create a component with multiple class’s without props or hard to override inlines.
Then you can export your “theme” like so:
This slight bit of indirection allows consumers to easily and more safely override your CSS. For example, if you had an app that wanted to change the font weight of the header, you could add there own CSS module named SuperComponent.cssm and Emeth would return the new CSS class for header and the existing class for container. Thus providing overrides, while at the same time preventing accidental styling that can happen with poorly name-spaced CSS.
In a sense the components CSS Module becomes the styling “contract”. A way to define what can be styled, and how it is styled by default. In addition, it does not require a bunch of class properties to be passed down the call chain, if this is attempted via propTypes, or HOCS (Higher Order Components) to do the injection.
To make things a little easier, if you are using Webpack, you can setup the import of the CSS Modules to happen automatically. A context file, like the one below will map the components CSS Module to the component, by component name.
So now a developer using the component would just need to create a CSS file in the same directory as this script and it becomes available to the respective component, able to override both the components created within the application and external components used by the application. In addition if a class is used in a component and not found in the components styling a warning appears in the console; super helpful for misspellings and other common errors.
Using CSS Modules gives you the best of CSS: debug-ability, pre-processors, 20 years of experience, tooling, namespaces. Without the infernal pain of inlining styles. With a little help it can also give you support for overrides, restyling, and a bit of documentation in the form a styling contract.
For a super simple example see this repo and let me know what you think.