Solve Your Specificity Headaches With CSS Modules

“CSS Modules” is nothing to do with the W3C, rather part of a suggested build process. It compiles your project, renaming the selectors and classes so that they become unique, scoped to individual components. Styles are locked into those components and cannot be used elsewhere unless you specifically say so!

Preamble

Nowadays we’re quite used to the idea of web technologies being the driving force behind apps; web apps, mobile, and desktop apps. But unlike simple static websites, apps are typically more dynamic, complex, and often consist of components even beyond what Bootstrap or ZURB Foundation offer. As an app grows in complexity, managing its CSS can be a hellish task.

Over time, myriad strategies have been developed, such as OOCSS, ITCSS, BEM, Atomic CSS, etc. to keep CSS organized, reusable, and (crucially) scalable. These strategies require that you and everyone in your team follow the conventions diligently.

However, sooner or later, complexity will creep in again and you’ll encounter style rules like the following:

The problem with CSS on many large scale websites and apps is that it’s so difficult to keep the specificity low that, at some point, adding !important can’t be avoided. And refactoring CSS on a large codebase is tricky as removing styles might break other components.

In this tutorial, we are going to look into “CSS Modules” and how it can help us to minimize these notorious CSS problems.

Using CSS Modules

In a nutshell, “CSS Modules” is a tool that renames CSS classes and IDs into unique selectors, allowing it to isolate the style rules locally to the assigned elements or components. Assuming we have a button, we might typically write its style rules as follows:

If you peek at large sites such as Facebook, Instagram, or Airbnb through your browser’s DevTools, you will find that the CSS classes and IDs are named with just this sort of pattern. Here’s an example from the Airbnb homepage:

Who named the class with seemingly random numbers?

Multiple Components

Using CSS Modules won’t make much sense if we have just one component, so let’s expand our example to three components and see how to configure our project to implement CSS Modules.

Creating a Component

In this second example, we’re going to build three components; a button with three different styles. We’ll call them the “primary button”, the “outline button” (also known as a “ghost button”), and the “clear button”. We’ll put these buttons in a separate directory. Each directory will contain index.css and index.js.

In the index.js, we create the element and assign the classes to the element, as follows:

Using the new import directive in ES6, we import the stylesheet and read the classes and the IDs as a JavaScript Object. Then we create an element and add the class named .button using native JavaScript Templating which was also introduced in ES6. Lastly, we export our element so that the element can also be imported and reused within the other JavaScript files.

At the time of this writing, not every browser has implemented the latest JavaScript features and syntax from the ES6 specification. Therefore, we will need Babel to transform those snippets into JavaScript syntax that is compatible in most browsers.

Our stylesheet, index.css, is plain CSS. It contains a number of selectors to style the button element.

One of the advantages of using CSS Modules is that we don’t have to worry about naming conventions. You can still use your favorite CSS methodologies like BEM or OOCSS, but nothing is enforced; you can write the style rules in the most practical way for the component since the class name will eventually be namespaced.

In this example, we name all the classes of our button component .button instead of .button-primary or .button-outline.

Working with CSS inside Shadow DOM I still have the old habit of using BEM notation even though I don’t *need* to. Style encapsulation FTW!

Compiling Modules With Webpack

When we load the index.js on an HTML page, nothing will appear in the browser. In this case, we will have to compile the code to make it functional. We will need to install Babel, Babel Preset for ES2015 (ES6) and Webpack along with the following so-called “loaders” to allow Webpack to process our source files.

babel-loader: to load .js files and transform the source code with the Babel core module.

style-loader: to inject internal styles taken from the css-loader into our HTML page using the <style>.

We install those packages with NPM and save them to package.json file as our development dependencies.

Webpack Configuration

Similarly to Grunt with its gruntfile.js or Gulp with its gulpfile.js, we now need to setup Webpack using a file named webpack.config.js. Following is the complete Webpack configuration in our project:

The configuration tells Webpack to compile our main JavaScript file, main.js, to /dist/js directory. We’ve also enabled the modules option in css-loader as well as set the class and ID naming pattern to [hash:base64:5]__[local]. We employ babel-loader to compile our ES6 JavaScript files.

Once we have the dependencies installed and the configuration is set up, we import all of our three buttons in the main.js file.

When we load /dist/js/main.js in the browser, we should see our buttons are added with the classes named following the pattern that we set in the css-loader. We can also find the styles added to the page with the styles element.

It works!

Composition

CSS Pre-processors such as LESS and Sass allow us to reuse styles by extending another classes or IDs from other stylesheets. With CSS Modules, we can use the compose directive to do the same thing. In this example, I’ve put the common style rules that are shared across our three buttons in another file and imported the class from the new file, as follows:

Once the code is recompiled and loaded in the browser, we can find the button is rendered with two classes. There are also now four style elements injected into the page which include the _3f6Pb__button class that holds the common style rules of our components.

Using CSS Modules in Vue

In a real project, we likely wouldn’t utilize CSS Modules using plain JavaScript. We would instead use a JavaScript framework like Vue. Fortunately, CSS Modules has been integrated to Vue through the vue-loader; a Webpack loader that will compile the .vue. Below is an example of how we would port our primary button into a .vue component.

In Vue, we add the module attribute to the style element, as shown above, to enable the CSS Modules. When we compile this code we will get pretty much the same result.

Wrapping Up

For some of you, this will be something completely new. It’s perfectly understandable if the concept of CSS Modules is a bit of a head-scratcher at first glance. So let’s recap what we’ve learned about CSS Modules in this article.

“CSS Modules” allows us to encapsulate styles rules by renaming or namespacing the class names, minimizing clashing on the selector specificity as the codebase grows.

This also allows us to write the class names more comfortably rather than sticking to one particular methodology.

Lastly, as style rules are coupled to each component, the styles will also be removed when we no longer use the component.

In this article, we barely scratched the surface of CSS Modules and other modern tools of web development like Babel, Webpack, and Vue. So here I’ve put together some references to look into those tools further.