Maintainable and Optimized CSS with CSS Modules

Follow us on LinkedIn for our latest data and tips!


Maintainable and Optimized CSS with CSS Modules

There have been many techniques proposed to help developers develop maintainable and scalable CSS, some of which oppose each other rather directly. BEM was designed to avoid class name clashes, too much specificity, and is organized around components. When developing apps with JavaScript frameworks such as Vue or React, which are component-based, BEM seems like a great match.

BEM has some drawbacks though. If you have a fairly standardized set of colors, spacing sizes, and such, you’re going to have a whole lot of property repetition, resulting in a very large stylesheet. In that article linked to in the previous sentence, the author recommends having only one instance of a property/value pair and using a list of selectors to apply it to the proper elements. There are also suggestions out there proposing only the use of helper classes and only using those classes in your HTML. Then, of course, you’re left with tons of repetition in the HTML rather than in the CSS.

What if we could work with a component-centric CSS structure but keep the CSS and HTML clear of a lot of the repetition? This is where CSS Modules come in.

CSS Modules

CSS Modules is a specification developed primarily to aid in creating local-scope CSS classes and seems to be largely inspired by the BEM methodology but adds the scoping so that you need come up with absurdly long class names. A build tool is used to convert your CSS classes (the ones you don’t mark as global anyway) to be unique, generally via a hashing method.

So how do you use the correct classes if they’re going to change once the application is built? Well, CSS Modules are meant to be imported into a JavaScript file, which will give you access to a mapping between your original class names and the compiled ones.

For instance, you could have a CSS file like this:

/* color-scheme.css */
.standardColors {
background: white;
color: black;

.invertedColors {
background: black;
color: white;

You can then import that CSS into a JavaScript module, and the import will be an object with the class mapping, which you can use in your HTML output:

import styles from './color-scheme.css'

export default 
<div class="${styles.standardColors}">This is standard</div>
<div class="${styles.invertedColors}">This is inverted</div>

The styles object has properties with names matching the classes in the CSS file and their values refer to the hashed versions of those classes. So this allows us to create a CSS file for each component and make our CSS similar to BEM without the need for the naming convention, but it doesn’t solve the problem of repetition. So let’s look at the next feature of CSS Modules: Composition.

For this we’ll need some more CSS files:

/* base-styles.css */
.cozyMargin {
margin: 5px;
.comfyMargin {
margin: 25px;
.cozyPadding {
padding: 5px;
.comfyPadding {
padding: 25px;
/* comfy-box.css */
.box {
composes: comfyMargin comfyPadding from './base-styles.css'
import styles from './comfy-box.css'

export default 
<div class="${}">This box is comfy</div>

The key here is in comfy-box.css where you see the composes “property”. This essentially allows .box to be composed of the same styles as the classes we added (comfyMargin and comfyPadding from the base-styles.css file in this case). How exactly is this accomplished though? To see that we need to looks at value of in the JavaScript: "box comfyMargin comfyPadding". These classes are actually getting hashed, so that’s not really what would be, but if they weren’t hashed, that’s what it would be. Once the CSS is all done processing (by a CSS Modules tool) and being combined, the full CSS file will look like this:

/* combined.css */

Note: Simply adding '-h45h' like I did below is not actually what would happen. It's just that way for simplicity in this example.

.cozyMargin-h45h {
margin: 5px;
.comfyMargin-h45h {
margin: 25px;
.cozyPadding-h45h {
padding: 5px;
.comfyPadding-h45h {
padding: 25px;
.box-h45h { }

And the HTML output would look like this:

<div class="box-h45h comfyMargin-h45h comfyPadding-h45h">This box is comfy</div>

If you then run the CSS through a minifier, the box-h45h rule will be completely stripped out. The class will still show up on the HTML element, so you can still see what component the div is and know where to go if you need to make changes to the component or its CSS. This was just a quick and dirty intro to CSS Modules, with plenty of concepts and features left out, so if you’d like to really learn about CSS Modules, I suggest reading Glen Maddern’s introduction and the CSS Modules spec documentation for more information, including how to actually add the build step into your project.

The Proposal

You can see from the examples above that if every component was composed of those helper classes, rather than writing their own margin and padding rules, there would be no repetition of those rules. This is what the proposal is all about, except on a larger scale that covers more than just margin and padding helpers:

  1. Write 1 or more stylesheets containing lots of helper/utility classes with as few property declarations as possible to keep each rule set as succinct and reusable as possible.
  2. Each component has a stylesheet comprised of rule sets that almost exclusively use compose rather than writing their own property declarations. There will likely be exceptions where some of the styles for a component may be better off not being composed of helper classes.
  3. “Super helpers” can be written to compose several low-level helper classes into oft-used combinations of helpers.

This methodology has several benefits:

  1. The helper classes can help enforce rules regarding the use of certain units, colors, spacing, fonts, etc.
  2. On the same note, having all related helper classes near one another can help make it obvious when there are too many different values for a certain property and some consolidation should take place.
  3. As mentioned earlier, the total size of the CSS file(s) being served to the user is much smaller because it removes duplication.
  4. You write your JavaScript and component CSS in a component-based way, which is simplest for development when using component-based frameworks, but the output is more optimized for delivery.

And that’s it. CSS Modules enable you to organize and structure your CSS in many different ways while still keeping everything component-based and not needing to worry about global name clashing. Of course, there are other legitimate ways, so if you think you have a better way, feel free to let us know. I’m sure we can all learn from one another.