How CSS modules solve global scope in CSS

CSS1 was release in 1996 and was introduced to separate the styling from the document on a website. Notice that we are talking about a document, a small file describing a website. CSS has always been global, which has been fine for smaller websites. Now websites and web applications are far more complex and bigger, which also means that we need to handle our CSS better to avoid classname collisions and accidentally changing CSS that effect unintended places. We will look at how this is solved by first going through how CSS doesn’t scale and later go through both BEM and CSS modules.

CSS

As mentioned above, CSS is global, which can be nice for general classes and smaller applications, but most of the time you want your selectors to be rather specific to not influence other parts of your website when changing or refactoring your code. You can use only CSS on a large website as well, but since everything is global, it can be hard to alter CSS without knowing that it won’t affect anything else.

In CSS you can chain your selectors, which gives specific selectors. However, this doesn’t solve naming collisions since you could have the exact same sequence somewhere else. Also each selector requires the browser to scan the DOM for the selector, which gives very unperformant CSS. Also they tend to become very complex and hard to follow as the website evolves. Example of a CSS chained selector:

BEM (Block Element Modifier)

One of the first solutions for solving how to scale CSS was BEM. BEM is a naming convention for CSS classes and has been around for a few years. If BEM is applied correctly and with consistently across the whole website, this can solve the global scope issue. The thought behind BEM is to structure your classes in different small blocks and within each block you have different elements which can be modified. If we compare to a real world example, think of a house. A house has doors, windows, walls etc. and this represents blocks. Doors have handles, windows and so on and this would represent elements. Modifiers in the door example would for example be color of the door, the material on the handle etc.

One parts about BEM that I like is that it lets you think about a good structure for your CSS classes. However, what I don’t like about BEM is that the CSS classnames tend to become very long as the website evolves. Also it’s quite hard to be consistent about naming your CSS classes with BEM and takes some getting used to. This doesn’t either guaranty that you won’t have naming collisions, specially if a project is worked on by several developers. I think it work fine for smaller projects, but doesn’t scale for larger applications.

CSS modules

So lets talk about CSS modules. CSS modules gives unique classnames to each class and does so with the help of a bundler, .i.e webpack, browserify etc. How we make each class unique is by replacing classnames with hashed classnames in each CSS file. This will make classnames in one CSS file unique to classnames in other CSS files. This limits naming collision to within each CSS file, which is quite more manageable even in larger web applications. This also means that we came reuse classes in different components, since a classname has the same hash for every use of that classname.

Lets go through an example of how this works with webpack. First we need the css-loader plugin, this plugin will solve CSS modules for us. We tell this plugin that we want to use CSS modules, then with this plugin, webpack will scan all our CSS files and replace the classnames with a hashed version of that classname. Which also means that this will break all our usage of our CSS selectors and I will later show you how this is solved.

After applying CSS modules to our webpack config, it should look something like this in the config for css-loader.

First we add option modules, to tell css-loader that we want to enable CSS modules. I also added localIdentName, which is a nice option for development mode. This will give a more friendly classname (Component__classname__hash). But skip this option for production and then only a hash will be used.

So now we have added CSS modules to our project, which means all classnames have been translated to the CSS module version of the classname. Next step is to connect the classname in each CSS file to the component that uses that classname.

In a component based framework like React, I like to have a CSS file for each component. So without CSS modules using a CSS file looks like this:

With CSS modules it looks like this:

So not that much difference in syntax, also notice that I changed my classname from kebab-case test-class to camelCase testClass. This is because I prefer writing style.testClass instead of style[“test-class”], but that only depends of preference.

So that is pretty much how to make CSS modules solve the global scope issue. I should also mention that CSS modules work with preprocessors, since this is just a webpack plugin. So fell free to use CSS modules together with SASS or LESS if that is your comfort zone.

Compose

Since we have went through most part of CSS modules, I also want to mention compose. Compose is a way in CSS to reuse other CSS classes. This works both in the same CSS file as well as across different files. I will illustrate with an example. Say you have a background color that you want to reuse throughout your application.

Colors.css

So we define our lightBlue class, and we can then reuse that class in other classes within the same file by writing the composes command. We can also reuse the lightBlue class in other CSS files, like in Wrapper.css

Wrapper.css

This is quite powerful and can be used to minimize repetitive CSS, create variables and to reuse general classes in more specific classes.

That all for me on CSS modules. Hope you like the article and please comment!

Fullstack software developer with interest of React, Angular, VueJs, Javascript and CSS