HTML & CSS
Article

Understanding the CSS Modules Methodology

By Hugo Giraudel

In the ever-changing world of front-end development, it is quite hard to find a concept that actually makes a difference, and to introduce it correctly enough so that other people are actually willing to try it.

When we look at CSS, I would say the last big shift that happened in the way we write CSS (on the tooling side) would have to be the arrival of CSS processors, mostly Sass, probably the most well-known one. There’s also PostCSS, which has a slightly different approach but ends up essentially in the same basket – browser-unsupported syntax goes in, browser-supported syntax goes out.

Now a new kid has joined the block under the name CSS Modules. In this article, I’d like to introduce this technique, tell you why it has some good points, and how you can get started with it.

What is a CSS Module?

Let’s start with the explanation from the official repository:

A CSS Module is a CSS file in which all class names and animation names are scoped locally by default.

Now, it is a little more complex than that. Because class names are scoped locally by default, it involves some setup, a build process, and a bit of magic that is sometimes hard to grasp.

But eventually, we can appreciate CSS Modules for what they are: a way to scope CSS to a component and escape the global namespace hell. No more struggling to find a good way to name your components, because the build step is doing that for you!

How does it work?

CSS Modules need to be piped in a build step, which means they do not work by themselves. See this as a plugin for either webpack or Browserify. It basically works this way: when you import a CSS file inside a JavaScript module (such as, but not necessarily, a React component), CSS Modules will define an object mapping class names from the file to dynamically scoped/namespaced class names that can be used as strings in JavaScript. Allow me to illustrate with an example:

Below is a very simple CSS file. The .base class does not have to be unique on the project as it is not the actual class name that will be rendered. It is just a kind of alias inside the stylesheet to be used in the JavaScript module.

.base {
  color: deeppink;
  max-width: 42em;
  margin: 0 auto;
}

And here is how you would use it in a dummy JavaScript component:

import styles from './styles.css';

element.innerHTML = `<div class="${styles.base}">
  CSS Modules are fun.
</div>`;

Eventually, this will generate something like this (when using it through webpack with the default setup):

<div class="_20WEds96_Ee1ra54-24ePy">CSS Modules are fun.</div>
._20WEds96_Ee1ra54-24ePy {
  color: deeppink;
  max-width: 42em;
  margin: 0 auto;
}

The way class names are generated can be configured to make them shorter, or to follow a specific pattern. It doesn’t really matter in the end (although shorter class names mean shorter stylesheets) because the point is that they are dynamically generated, unique, and mapped to the correct styles.

Possibly valid concerns

So this is how it works. And now, you must be thinking “what on earth is this thing? I don’t even…”. Well, hold on! I hear you. Let’s tackle those concerns one by one, shall we?

It looks friggin’ ugly!

That is true. But class names are not intended to be beautiful; they are intended to apply styles to elements. And this is precisely what they do, so I would argue this is not a valid concern.

It is a pain to debug!

As soon as there is a build step to do some processing on your stylesheet, it is a pain to debug. Sass is not any easier. This is why we have sourcemaps, which you can set up for CSS Modules as well.

I would actually argue that despite the unpredictability of class names, it is not that hard to debug as styles are, by definition, specific to modules. If you know what module you are inspecting in the dev tools, you know where to look for the attached styles.

It makes styles non-reusable!

Yes and no. On the one hand, yes, but this is actually the point: it ties styles to a component to avoid global styles leaking and conflicts. Which is a good thing, I’m sure you will concede.

On the other hand, it is still possible to define global classes (with :global()) such as helpers which are preserved as authored, making style abstracting as easy as usual. These classes can then be used in your JavaScript components.

:global(.clearfix::after) {
  content: '';
  clear: both;
  display: table;
}

CSS Modules also have a way to extend styles from another module, which basically works the same way as @extend in Sass. It does not copy the styles but concatenates selectors to extend styles.

.base {
  composes: appearance from '../AnoherModule/styles.css';
}

It needs webpack, Browserify or [whatever] tool!

This is the same way Sass needs to compile the .scss files to actual CSS, and PostCSS needs to process the stylesheets to make them compatible with browser-understood styles. The build step is already there anyway.

Why are we even discussing this?

Well, I am not entirely sure that in the future CSS Modules will stay the way they are currently, but I do think this is a sensible way to write styles. Having a massive global stylesheet is not suitable for large sites decoupled in tiny components.

The unique namespace of CSS makes it both powerful and very fragile at the same time. Such a solution, be it CSS Modules or any future tool based on this idea, allows us to preserve the strength of style-sharing with global classes while making it a breeze to avoid naming conflicts with scoped styles. Win-win.

Getting started

As said above, you will need either webpack or Browserify to enable CSS Modules.

Webpack

Let’s start with the webpack version. In the webpack.config.js, add the following configuration to tell webpack to treat CSS files with CSS Modules:

{
  test: /\.css$/,
  loader: 'style-loader!css-loader?modules'
}

Right now it will inject styles inside a <style> element in your page. This is probably not what you want, so let’s make a proper stylesheet out of it thanks to the extract text plugin for webpack:

{
  test: /\.css$/,
  loader: ExtractTextPlugin.extract('style-loader', 'css-loader?modules')
}

That’s pretty much it for webpack.

Browserify

I’ve only ever used Browserify through the command line, so it is a bit trickier I guess. What I did was add an npm script to my package.json file, like so:

{
  "scripts": {
    "build": "browserify -p [ css-modulesify -o dist/main.css ] -o dist/index.js src/index.js"
  }
}

This tells Browserify to run on src/index.js, output dist/index.js, and compile a stylesheet at dist/main.css through the css-modulesify plugin. If you want to add Autoprefixer to it, you can complete the command like so:

{
  "scripts": {
    "build": "browserify -p [ css-modulesify --after autoprefixer -o dist/main.css ] -o dist/index.js src/index.js"
  }
}

As you can see, you can use the --after option to process your stylesheet after having it compiled.

Conclusion

As of today, the CSS Modules system and ecosystem is a bit raw, as you can see from the Browserify configuration. But I am convinced it is going to get better as more people realize this is a sustainable solution for small to large projects.

I think the idea behind CSS Modules is the way to go. I am not saying this library in particular is the best solution available, but it definitely has some features with regard to the way CSS should be written: modular, scoped, yet still reusable.

As a further read, I recommend this introduction to CSS Modules by Glen Maddern, creator of the project.

Free Guide:

7 Habits of Successful CTOs

"What makes a great CTO?" Engineering skills? Business savvy? An innate tendency to channel a mythical creature (ahem, unicorn)? All of the above? Discover the top traits of the most successful CTOs in this free guide.

  • http://cssmojo.com/ Thierry Koblentz

    I think you should mention **bloat** in the “Possibly valid concerns” section as writing styles like this creates a lot of redundancy in styles sheets.

    • http://hugogiraudel.com/ Hugo Giraudel

      CSS Modules does not create a lot of redundancy in stylesheets, the same way Sass does not create a lot of redundancy in stylesheets. It depends how you use it.

      • http://cssmojo.com/ Thierry Koblentz

        In that section you say:

        > It makes styles non-reusable! […] it ties styles to a component to avoid global styles leaking and conflicts.

        The above is what creates redundancy; because you end up multiplying declarations inside many different rules. Also, anything that ties many styles to a component should reduce class repetition in the markup (but repetition is good for Gzip).

        > the same way Sass does not create a lot of redundancy in stylesheets

        This is not about how it is written but what is written. Plain old CSS can create a lot of redundancy too.

        • http://heliosstudio.ca/ Paul d’Aoust

          Actually, with CSS Modules you don’t have to duplicate declarations in style rules. In fact, that’s one of the things CSS Modules is trying to avoid. I think it’s the DRYest CSS reuse mechanism I’ve seen. it’d be quite suited for Atomic CSS, I think — you organise your atoms into modules in whatever way makes sense to you — perhaps you have a `margins.css` for all your margin rules — and then you import those modules into your components. For instance (using an older ACSS style):

          margins.css:

          .m-1 { margin: 1em; }
          .m-2 { margin: 2em; }

          colours.css:

          .bg-info { background-color: blue; }
          .bg-warning { background-color: orange; }

          messagebox.css:

          .mb { composes: m-1 from “margins.css”; }
          .mb-warning {
          composes: mb;
          composes: bg-warning from “colours.css”
          }

          Then when you try to use .mb-warning in your markup, it’ll generate a class attribute with all class names composed in it. In debug mode that looks something like:

          messagebox.js:

          var messagebox = require(‘messagebox.css’);

          export default “;

          output:

          The nicest thing, to me, is that the application of CSS atoms isn’t coupled to the markup; it’s contained entirely in the CSS. This should help out immensely with using ACSS and responsive design together.

          • http://cssmojo.com/ Thierry Koblentz

            Hi Paul,

            I’m not sure I am following you as you mention “.mb-warning” in your example and “Atomic CSS” at the same time but as far as I know, the former is an anti-pattern when it comes to Atomic CSS (multiple declarations per rule). Also, Atomic classes found in the markup are highly repetitive (Gzip-friendly), unlike something like “margins__m-1__fa93 colours__bg-warning__d1d6”.
            Once again, I am not talking about how things look from the dev point of view, but from the output: content of the styles sheet and @class values in the markup. Also, please note that I am not saying “CSS module” is “evil”, I’m just saying that – in my opinion – the “Valid concerns” section in this article seems to ignore a couple of points.

            After all, the given definition is:

            > A CSS Module is a CSS file in which all class names and animation names are scoped locally by default.

            And as we know, narrowing the scope does not lead to **less** CSS…

          • http://heliosstudio.ca/ Paul d’Aoust

            Hi, Thierry. You’re correct; the `.mb-warning` class is not Atomic — it’d be a way of composing and reusing a collection of atoms. But instead of doing it in markup, and remembering to use the same atoms every time you create a message box, you’ve got a component — let’s call it a ‘molecule’ — that you can use over and over again.

            After building your HTML, because your molecules are nothing but compositions of atoms and don’t add any style declarations of their own, all you get in the markup is atomic class names. They’re rather ugly ones — but because they represent the original atoms, they’ll appear over and over again in the markup and will be gzippable. It’s just that you don’t have to worry about consistently remembering what atoms a message box is supposed to be composed of anymore.

            * Note: I haven’t looked into ACSS tooling recently; is there something that helps you compose atoms into reusable components in this fashion? Atomizer?

          • http://cssmojo.com/ Thierry Koblentz

            > They’re rather ugly ones — but because they represent the original atoms, they’ll appear over and over again in the markup and will be gzippable.

            Your example shows otherwise:

            For better repetition, you’d need to refer to that styling (which regroups different styles) via multiple/reusable classes (single purpose principle).

            There is a very big difference between putting small pieces together via a tool (i.e. @extend) and putting them via the markup (inside @class).

            Anyway, it feels like we’re hijacking these comments… Not sure this is the place for a lengthy debate ;)

          • Neverfox

            How does the example show otherwise? It seems to exactly show a case of atomic classes appearing in the markup (which would be repetitive across the markup). Perhaps I’m misunderstanding your point, because I don’t understand what you mean by “you’d need to refer to that styling (which regroups different styles) via multiple/reusable classes (single purpose principle)”.

          • http://cssmojo.com/ Thierry Koblentz

            Because “margins__m-1__fa93 colours__bg-warning__d1d6” is a single class tied to multiple styles but Atomic CSS is about using multiple classes – each one tied to a different style.

          • Neverfox

            That’s not right. There are two classes (there’s a space there), each tied to a different single CSS attribute, just like Atomic CSS.

          • http://cssmojo.com/ Thierry Koblentz

            Wow! I’d totally miss that space :-( I looked at that class many times and never noticed there were 2 in there. From *that* example, I assumed selectors were grouped (not a la Atomic). So yes, in this case, it’s all good. Thanks for the extra pair of eyes…
            I don’t know why Paul didn’t notice/pointed out earlier that I was totally missing the fact that that class was actually 2 different ones. Anyway, I now have a question regarding the trailing characters (i.e. “__fa93”), what is that? Namespacing? Would the class for another module using “margin:1em” be named the same?

          • Neverfox

            No worries. I’m actually glad to know that I understood your concern enough to point out the key piece of information.

            As for the trailing characters, they represent a optional hash that was added as a result of the configuration of css-loader (in fact the whole name of the class is configurable, e.g. css-loader?localIdentName=[path][name]—[local]—[hash:base64:5]), with the idea being that it raises the likelihood that the name won’t collide with any global CSS that might be lurking around the site. And yes, it will be the same hash everywhere the module is used.

          • http://cssmojo.com/ Thierry Koblentz

            But the concern about the hash is not if it is the same one associated to that module but rather the same one associated to *that* style (margin:1em). In other words, having rules in the styles sheet like the ones below is NOT Atomic CSS:

            /* rule attached to foo module */
            .margins__m-1__fa93 { margin: 1em;}

            /* rule attached to bar module */
            .margins__m-1__ts33 { margin: 1em;}

            or if the rules are optimized:

            /* margin for modules foo and bar */
            .margins__m-1__fa93,
            .margins__m-1__ts33 { margin: 1em;}

            This is why the use of a hash may not be going in the right direction. Atomic CSS is about not tying a style to a module via a specific selector in the styles sheet but rather via the use of a class in the markup:

            .margins__m-1 { margin: 1em;}

            Not following this basic Atomic CSS “rule” leads to redundancy in styles sheets. Of course, it’s a different story if all modules spit out selectors sharing the same hash for the same style.

    • http://hugogiraudel.com/ Hugo Giraudel

      CSS Modules does not create a lot of redundancy in stylesheets, the same way Sass does not create a lot of redundancy in stylesheets. It depends how you use it.

  • http://moox.io/ MoOx (Maxime Thirouin)

    About the debug you forgot to mention the possibility to use a pattern for the class. Ugly hash is not the only solution. Something like "css-loader?modules&localIdentName=[path][name]--[local]--[hash:base64:5]" might help (path/name of the file, + local identifier + a little hash).

    • http://hugogiraudel.com/ Hugo Giraudel

      True, thanks for the heads up. Any reason not too prefer a short hash?

      • http://moox.io/ MoOx (Maxime Thirouin)

        Like you said, this is just for debugging. If you use the full path/name/local you are likely to have a unique class, so hash is useless here.
        But in production long hash is preferred as it is safer.

      • http://moox.io/ MoOx (Maxime Thirouin)

        Like you said, this is just for debugging. If you use the full path/name/local you are likely to have a unique class, so hash is useless here.
        But in production long hash is preferred as it is safer.

  • Alexander Diachenko

    Please note, that :global() is mostly used for complex expressions such as this :global(.ui.grid).container where both namespaced and global styles are present. It’s not actually required for defining global styles. At least this is the case when using react-css-modules. You can even process both your global and component stylesheets with the same loader.

  • http://tassedecafe.org Jérémy Heleine

    Interesting. I still don’t know if I love them, but I think about giving CSS Modules a try in a future project, just to test. Thanks for the discovering!

  • Grigore

    Or wait for web components and we’ll have local CSS scope by default

  • Steven Martin

    I think you should update the part where you say It looks friggin’ ugly!
    Here is what my title class on a Header component would look like
    Heading___title___WjF09
    All you need is to include the following to your loader to fix this issue:
    &localIdentName=[name]__[local]___[hash:base64:5]

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in Front-end, once a week, for free.