Sakalim

Sakalim

About Me

over 4 years ago

How to isolate component styles in React using CSS modules

    Frontend

{React is a library for building front-end applications by creating components and composing them into a complex user interface. A component is a Javascript function/class, usually written in .jsx files, that manages its state, DOM structure, behaviors, and user interactivity. The component style is defined either as a separate style (.css/.scss) file or directly in the Javascript file using a concept named css-in-js.

When using a separate style file, by default, all styles are global, as demonstrated later in this post, and you need to work harder to prevent collisions between components styles. This post suggests ways to encapsulate components styles and making them unique per component easily.

Css-in-js or separated styles file?

Writing styles in a separate file is trivial as you write standard css definitions. Usually, you create a .css/.scss file with the same name of the component and then import it in the component Javascript file, making it part of the deployed package. Alternatively, you can use a library that follows the css-in-js principles and directly writes the styles inside the Javascript file using JSON objects. CSS-in-js is not a particular library; it represents a group of libraries following the same ideas.

Both ways are popular; each has its benefits and drawbacks. When using either, you write the styles next to the component definition, so probably expect that the component styles will affect only that component. Effectively at runtime, all the components' styles are added to the page head together, so there is no separation between components styles, and collision is inevitable.

CSS-in-js prevents collisions by generating unique selectors for each component. With separated .css/.scss file, it is the responsibility of the library you choose to prevent the collision, if anything.

Tip: When switching between css definition to JSON representation, you can assist with an online converter like css to React.

Building a box component with styles

The screenshot below shows two boxes, both with green background, even though the first one should have a red background.

Two green boxes instead of a red box and a green box

Here is the code that leads to that unfortunate outcome.

GreenBox.css (the styles file)

.box {
    background: green;
}

GreenBox.jsx (the Javascript file)

import React from 'react';
import './GreenBox.css';

export function GreenBox() {
  return <div className="box">
    Green box
  </div>
}

RedBox.css (the styles file)

.box {
    background: red;
}

RedBox.jsx (the Javascript file)

import React from 'react';
import './RedBox.css';

export function RedBox() {
  return <div className="box">
    Red box
  </div>
}

The problem

We defined two different components; each has its styles. Both have a css selector named .box with a different background color. When compiling the code, the bundler, a tool that generates the deployable files, groups all the styles together and, at runtime, inject them to the page header. Effectively at runtime, they are not bound to the original component, and other rules apply to determine which style to show when. In our case, the computed style of .box will show green background for both components. A computed style is a set of all the values of all the css properties relevant to the element after resolving all the duplication and performing the computation of the relevant properties.

If you wonder why the browser prioritized the green background over the red one, it happens because the order of CSS styles matters. You can read more in article Precedence in CSS published at css-tricks.

The computed style of the box classname as a result of showing both components in the page

How to avoid that problem

There are popular methodologies, like BEM, ABEM, Atomic, SMACSS, that provide conventions to prevent name collisions in the browser.

Using conventions is excellent in general, but with modern libraries, there are some great alternatives, and you will probably prefer enforcing it at the API level to automate the process and leveraging authoring tools for component-based applications.

To automate it instead of counting on conventions, We will use css module css modules, a library that will make sure each component is using unique selectors at runtime

Prepare your project to support css modules

CSS modules can be used in any Javascript project regardless of the devop tools you used to setup your project. But with "Create React App", a powerful toolchain to build React applications, it is even easier as "Create React App" comes with css modules already activated and ready to be used.

I must admit that it is not a straightforward process to set up css modules manually; I'm not sure why but the css modules documentation doesn't provide an easy guide to follow. I suggest you check if the tool you are using already supports css modules. Consider other alternatives or google for a guide that shows you how to tweak your bundler configuration.

If you are using "Create React App", go ahead to the next section, otherwise you can check out the CSS modules examples section in their documentation site.

How to fix our boxes components using css modules

This section focuses on using css modules with the "Create React App" tool.

To use css modules with any component in your project, you need to rename the style file by adding .module before the file extension. Refactoring the example from before will result in the desired outcome.

Two boxes that are using the same class name which becomes unique at runtime

(Run example in CodeSandbox)

GreenBox.module.css

Change the file name, add .module to the name. In the file content, you can use any css class name you want.

.box {
    background: green;
}

GreenBox.jsx

Import the .module.css file with default module classes. Then, use classes when setting the child className property.

import React from 'react';
import classes from './GreenBox.module.css'; // <--- 1. import classes from the module css file

export function GreenBox() {
  return <div className={classes.box}> {/* <--- 2. use classes file extension */}
    Green box
  </div>
}

RedBox.module.css

Change also this file name, add .module to the name. In the file content, you can use any css class name you want.

.box {
    background: red;
}

RedBox.jsx

Import the .module.css file with default module classes. Then, use classes when setting the child className property.

import React from 'react';
import classes from './RedBox.module.css'; // <--- 1. import classes from the module css file

export function RedBox() {
  return <div className={classes.box}> {/* <--- 2. use classes file extension */}
    Red box
  </div>
}

Problem solved

Your components are now using css modules, which guarantee that you will never have name collision between components again. The image below shows the class names at runtime with and without CSS modules.

Tips when using CSS modules

Tip #1 - don't worry about awkward class names formatting

In your javascript files, all class names are converted to lower case to keep the Javascript convention for constants. For example, class name .home-view is converted to homeView and can be used as classes.homeView.

Tip #2 - pick simple names

You are encouraged to use simple class names, at runtime each name will become unique but will still include the original name to simplify code debugging.

Tip #3 - css modules library is not tight to "Create React App"

As mentioned earlier, css modules are not part of the "Create React App" tool and can be used by any project regardless of the tools you are using. If you set up CSS modules on your own, make sure you enable important css modules plugins like global preprocessor.

Tip #4 - css modules library supports nested class names

When using The .scss file, you can continue using nested classes. The CRA tool is smart enough to handle nested classes.

GreenBox.module.css

.box {
    background: green;
    .text {
        font-size: 30px;
    }
}

GreenBox.jsx

import React from 'react';
import classes from './GreenBox.module.css';

export function GreenBox() {
  return <div className={classes.box}>
    <div className={classes.text}>Green box </div>
    </div>
}

In the example above, You can nested class names directly from classes like classes.text.

Tip #5 - avoid using global class names with your own components

If you have a module that is using a class name from a global CSS file, it might indicate that you are missing a component.

Imagine you have a black box class name that blocks interactions with elements below it. Instead of just using a global class .black-box and calling it from multiple components; you should have a component named BlackBox with a matching CSS module. This way, you keep your code more straightforward and easier to maintain.

Below is a partial example demonstrating the Box component using the global class name black-box that is defined in a global CSS file.

return (
  <div className={classes.box}>
    <div>Some important content</div>
    { isBlocked && <div className="black-box" /> }
  </div>
)

Below is an example of a BlackBox component that will handle this feature.

return (
  <div className={classes.mapView}>
    <div>Some important content</div>
    <BlackBox visible={isBlocked} />
  </div>
)

Tip #6 - css module library lets you override global class names in css file

You can declare global selectors both in .css files and .scss files. Using global selectors is useful if you want to override styles of third party components.

In your css file, write the following to declare a global selector that will not be handled by the CSS module compiler.

:global {
    .global-class-name {
        color: green;
    }
}

Tip #7 - css module library lets you override global class names in scss file

Setting global class names in scss files is even more robust as you can limit a global selector to affect only nested component children.

.box {
    :global {
        .global-class-name {
            color: green;
        }
    }
}

To read more about global selectors, see their documentation.

Final words

In this post, I demonstrated a way to avoid style collision between components when using the "Create React App", with minimal effort by leveraging the power of css modules.

I have used it in many React projects that didn't require advanced styling support. Still, I recommend trying out css-in-jss next time you start a new project and play with it before settling with css styles file. It might surprise you.

Photo by Amanda Vick on Unsplash