Advertisement
  1. Web Design
  2. HTML/CSS
  3. JavaScript for Designers

Solve Your Specificity Headaches With CSS Modules

Scroll to top

“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:

1
html.progressive-image.js [data-progressive-image],html.progressive-image.js [data-progressive-image] * {
2
    background-image: none !important;
3
    mask-image: none !important;
4
    opacity: 0
5
}
6
.main #section-enhanced-gallery-heroes.homepage-section.enhanced-gallery .with-single-item {
7
    transform: translate(0, 0) !important
8
}

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.

Note: check out the repo on Github for supporting code examples.

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:

1
.button {
2
    background-color: #9b4dca;
3
    border: 0.1rem solid #9b4dca;
4
    border-radius: .4rem;
5
    color: #fff;
6
    cursor: pointer;
7
    display: inline-block;
8
}
9
.button:focus,
10
.button:hover {
11
    background-color: #606c76;
12
    border-color: #606c76;
13
    color: #fff;
14
    outline: 0;
15
}

With CSS Modules these style rules will be renamed into something like:

1
._12We30_button {
2
    background-color: #9b4dca;
3
    border: 0.1rem solid #9b4dca;
4
    border-radius: .4rem;
5
    color: #fff;
6
    cursor: pointer;
7
    display: inline-block;
8
}
9
._12We30_button:focus,
10
._12We30_button:hover {
11
    background-color: #606c76;
12
    border-color: #606c76;
13
    color: #fff;
14
    outline: 0;
15
}

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:

Airbnb look through the DevToolsAirbnb look through the DevToolsAirbnb look through the DevTools
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:

1
// 1. Import the styles from index.css file.

2
import styles from './index.css';
3
4
/**

5
 * 2. Creating a button element and add the class from index.css.

6
 * @type {String}

7
 */
8
const button = `<button class="${styles['button']}">Save Changes</button>`;
9
10
// 3. Export the button to be used in the other files.

11
export default button;

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.

1
/* 1. Primary Button */
2
.button {
3
    background-color: #9b4dca;
4
    border: 0.1rem solid #9b4dca;
5
    border-radius: .4rem;
6
    color: #fff;
7
    cursor: pointer;
8
    display: inline-block;
9
    font-size: 1.1rem;
10
    font-weight: 700;
11
    height: 3.8rem;
12
    letter-spacing: .1rem;
13
    line-height: 3.8rem;
14
    padding: 0 3.0rem;
15
    text-align: center;
16
    text-decoration: none;
17
    text-transform: uppercase;
18
    white-space: nowrap;
19
}
20
/* More styles of the Primary Button here */

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.

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 BabelBabel 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.
  • css-loader: to load .css files.
  • 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:

1
var path = require('path');
2
3
module.exports = {
4
    entry: './src/main.js', // 1.

5
    output: {
6
        path: path.resolve(__dirname, 'dist/js'),
7
        filename: 'main.js'
8
    },
9
    module: {
10
        loaders: [ // 4.

11
            {
12
                test: /\.css$/,
13
                use: [
14
                    {
15
                        loader: 'style-loader'
16
                    }, {
17
                        loader: 'css-loader',
18
                        options: {
19
                            modules: true,
20
                            localIdentName: '[hash:base64:5]__[local]'
21
                        }
22
                    }
23
                ]
24
            }, {
25
                test: /\.js$/,
26
                exclude: /node_modules/,
27
                use: [
28
                    {
29
                        loader: 'babel-loader',
30
                        options: {
31
                            presets: ['es2015']
32
                        }
33
                    }
34
                ]
35
            }
36
        ]
37
    }
38
}

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.

1
// Import the button elements;

2
import ButtonPrimary from './button-primary';
3
import ButtonOutline from './button-outline';
4
import ButtonClear from './button-clear';
5
6
// Add the element to the content;

7
document.getElementById('content').innerHTML = `${ButtonPrimary}${ButtonOutline}${ButtonClear}`;

Then run the webpack command, as follows:

1
./node_modules/.bin/webpack

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.

Elements Tab on Google Chrome DevToolsElements Tab on Google Chrome DevToolsElements Tab on Google Chrome DevTools
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:

1
.button {
2
        
3
    /* Extend .button class to apply the basic button styles */
4
    composes: button from "./button.css";
5
    
6
    color: #fff;
7
    background-color: #9b4dca;
8
    border: 0.1rem solid #9b4dca;
9
}

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.

1
<template>
2
<button v-bind:class="$style.button"><slot></slot></button>
3
</template>
4
5
<script>
6
export default {
7
    name: 'button-primary'
8
}
9
</script>
10
11
<style module>
12
.button {
13
    
14
    /* Extend .button class to apply the basic button styles */
15
    composes: button from "./button.css";
16
    
17
    color: #fff;
18
    background-color: #9b4dca;
19
    border: 0.1rem solid #9b4dca;
20
}
21
22
.button[disabled] {
23
    cursor: default;
24
    opacity: .5;
25
}
26
27
.button[disabled]:focus,
28
.button[disabled]:hover {
29
    background-color: #606c76;
30
    border-color: #606c76;
31
}
32
33
.button:focus,
34
.button:hover {
35
    background-color: #606c76;
36
    border-color: #606c76;
37
    color: #fff;
38
    outline: 0;
39
}
40
</style>

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.

Further Reference

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.