Practical Guide to React and CSS Modules

Web developers have spent tremendous time and effort to make reusable components. One of the problems has been CSS and its cascading nature. For example, if a developer creates a component that displays a tree structure, how can she/he ensure that CSS class (say, .leaf) used in the component will not have side-effects in other parts of the application. New methodologies and conventions have been created to tackle selector issues. BEM and SMACSS are both widely used methodologies that have served web developers well, but they are far from ideal solutions. This blog post will explain what flaws naming convention–based methodology has, what CSS Modules are, and how these modules can be used in a React application.

The problem with cascading

I am creating a reusable select list (aka drop-down list) as an example of global style issues. Styling the <select> element itself doesn't feel right because there might be a need for unstyled native element or completely different styling in other areas of the website. Instead, I use BEM syntax to define classes:

If I created a new class called item without the select__ suffix, my team might be in trouble if someone else wanted to use the common name item. It will not matter whether a third-party library or a team member is creating the CSS framework for the project. Using BEM helps you avoid this problem by indicating the context select.

The BEM syntax represents a step towards components, as the "B" in BEM stands for "Block," and blocks can be thought of as light-weight components. Select is a component that has different states (select--loading) and children (select__item).

Unfortunately, using a naming convention and thinking in terms of components doesn't solve all the selector issues. Avoiding a name collision is still not guaranteed, and the verbosity of the naming scheme increases the risk of typos and requires a disciplined team where everyone understands the conventions 100% accurately. Typos include having single dash instead of two, mixing modifier (--) and block (__), etc.

CSS Modules to the rescue

The definition of "CSS module" is as follows:

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

The key thing is scoped locally.

To illustrate this concept, let's create JavaScript and CSS files that will define a component.

The CSS file has a lot less noise than the BEM version because it doesn't contain suffixes and extra characters to repeat the context. Why is it that I could remove a suffix such as .select-- without causing problems?

The import statement in the JavaScript file loads the CSS file and converts it to an object. In the next chapter, I'll show you how to setup the build environment to support importing CSS files.

Each class name from the CSS file is a property of the object, in the example above, styles.select, styles.icon, etc.

If the property name is the class name, then what is the value of that property? It is a unique class name, and the uniqueness ensures that styles don't leak into other components. Here is an example of a hashed class name: _header__1OUvt.

You might be thinking, "That looks awful." What is the point of changing meaningful class names to cryptic code?

The main point is that such an identifier is guaranteed to be globally unique. Later in the guide, we will modify identifier creation so that it will have a more human readable identifier but still be unique.

The key benefits of having locally scoped CSS:

one step towards modular and reusable components that will not have side effects

cleaner CSS

avoidance of monolithic CSS files, as each component will have its own file

Disadvantages:

not as human-readable DOM

a bit of initial preparation to get the build step working

CSS Modules require a build step, but the good news is that various bundling tools support that step for both the client and server JavaScript source code. You can also use CSS Modules with most of the UI libraries.

Is the Create React App using CSS Modules? I can verify that it is by looking at the file App.js. The CSS file is being imported, but it is not assigned to any variable, and all the className definitions are using strings instead of dynamic values.

At this point, the Create React App isn't supporting CSS Modules, so I need to change the configuration to enable support.

Configure the Create React App to support CSS Modules

To access the underlying build configuration, I need to run the eject command. Note: there is no going back after you do this.

npm run eject

I can now see the config folder and webpack configuration files.

The Create React App is using webpack for all assets, so webpack.config.dev.js is the correct configuration file to modify.

I look for a section that defines what to do with the CSS files.

{
test: /\.css$/,
loader: 'style!css?importLoaders=1!postcss'
},

Changing it to the following will break the styling of the site for a moment because the configuration will enable CSS Modules, but the component is not changed to support CSS Modules. While I was modifying the webpack configuration, I changed the naming convention to have both human-readable part in it and the hash to keep it unique:

Notice that I changed loader to loaders to give the configuration section an array instead of a single string.

What are those loaders doing? The file webpack.config has a comment section that describes style and CSS loaders:

"style" loader turns CSS into JS modules that inject <style> tags.

"css" loader resolves paths in CSS and adds assets as dependencies.

The modules keyword next to the css-loader enables CSS Modules. The setting localIdentName changes the generated class name so that it contains a React component name, a class name, and, the unique hash id. This configuration change will make debugging much easier because you will be able to identify the problematic component.

Use CSS Modules in React

I can verify that the configuration works by adding a console.log statement to the import statement.

Changing import './App.css'; to

import styles from './App.css';
console.log(styles);

I get the following output to the browser console:

The classes are now unique, but they are not used in the React components. There are two steps that I have to follow to get styles applied to the React components. First, change the names of the styles to camel case. Second, change the classNames attributes so that they use imported classes.

It is not mandatory to use camel case capitalization, but, when accessing classes programmatically, it is easier to access styles.componentName than styles["component-name"].

I have now made all the required configuration changes and changed the component so that it uses CSS Modules.

How to break CSS Module boundaries when needed

The setup described in the previous section works as a solid base for a React project, but developers tend to soon realize that they need a way to share styles. In this context, "sharing" means explicitly indicating that a component should inherit something from the base styles.

I would make a tiny change to the variable naming: use SASS syntax for variable names that are preceded by a dollar sign. The reason for this change is that overriding standard values like blue makes the CSS file less understandable because you can never be sure whether the value has been overridden.

Conclusion

In this guide, I started with the problem with global CSS and explained why CSS Modules improve the situation by introducing scoped CSS and pushing us towards component-based thinking. Also, I went through the way to easily start experimenting with CSS Modules using the React Starter Kit.

If CSS Modules are used in the backend build step, browser support is not an issue. Browsers receive regular CSS from the server, so there is no way to negatively affect user experience with CSS Modules. Instead, we improve user experience by decreasing the risk of broken layout. The webpack with loaders configured to accept CSS Modules hasn't caused any issues, so I would give it a thumbs up without hesitation.

Hi there! I'm Tatu, an independent Web developer; I help clients on building successful SaaS products. If you're interested in freelancing, you should check my email list How to get started as a freelancer. In my email list, I share private learnings from my own journey, including information about pricing, finding clients, working with the clients, how to get started, etc.
Please don't hesitate to reach out by sending an email or via Twitter.

Are you a Web Developer and been considering freelancing?

In my email list I share private learnings from my own journey, including information about pricing, finding clients, working with the clients, how to get started, etc. Sign up now to learn more + to personally ask me questions!