Comments

Using CSS Modules in React

CSS Modules in React

If you have some experience dealing with stylesheets in large web applications, then you likely know that, sometimes, naming classes can be challenging and conflicts are not only possible, but probable if you’re not careful enough. Have you ever asked yourself: How can I make CSS classes locally scoped to a component? In this article, you’ll find an excellent way of doing so, CSS Modules. Just keep reading!

What is a CSS Module?

CSS Modules logo

A CSS module is, in simple words, a CSS file. But with a key difference: by default, when imported, every class name and animation inside a CSS module is scoped locally to the component that is importing it. This allows you to use virtually any valid name for your classes, without worrying about conflicts with other class names in your application. CSS modules also allow you to extend one or more classes, inheriting their styles. This concept is called class composition.

Requirements

Do you use create-react-app v2+? Then you’re good to go. Feel free to skip this section and move to the next one. CRA comes with out-of-box support for CSS Modules. I totally recommend it for new projects. But if you have an existing React application that uses an older version of CRA or simply doesn’t use it at all, you have some options:

How to use CSS Modules?

Using CSS modules is really, really simple:

  1. First, create a normal CSS file. If your application uses create-react-app v2+, you need to name this file following the [name].module.css convention.
  2. Add CSS classes to this file. Any valid CSS class name is acceptable, but it’s recommend to use camelCase for classes with more than one word. The reason for this is that you’ll access these classes later as properties of a JS object and, if you want to use the dot notation (e.g. yourObj.someProperty), hyphens are not syntactically allowed. You could also use the brackets notation (e.g. yourObj[‘some-property-with_a-peculiar_name’]), but this additional verbosity can be avoided by simply naming your classes using plainOldCamelCase.
  3. Import the module you’ve just created from within your component, like this:
    import styles from ‘./data-grid.module.css’;
  4. To use a class defined in your module, just refer to it as a normal property from the styles object, like:
    <header className={styles.header}>…</header>
  5. When your application runs, the class names in the rendered DOM will be automatically mangled to prevent global conflicts. The runtime name will be something like data-grid_cell__3ZxsR instead of just cell, for example.

A picture is worth a thousand words:

How do CSS Modules work?

And here’s the sandbox for the above example:

 

Class composition

Another great feature provided by CSS Modules is class composition. This allows developers to compose a new class by inheriting styles from other classes. Let’s understand it better with a Navbar component example:

import React from 'react';
import styles from './nav-bar.module.css';

export const NavBar = (props) => (
  <nav>
    {
      props.links.map((link) => {
        let className = styles.normalLink;
        if (link.standout) className = styles.standoutLink;
        if (props.currentRoute === link.route) className = styles.currentRouteLink;

        return (
          <a className={className} href={link.route}>{link.title}</a>
        );
      })
    }
  </nav>
);

In the above component, we’re iterating over a list of links which must be passed as a prop. A link is styled with one single class at a time and each possible state is represented by a class (normal, current route and standout).

Here’s the component that imports it:

import React from 'react';
import NavBar from './nav-bar';

export default () => (
  <header>
    <NavBar currentRoute="/contact"/>
  </header>
);

And finally, let’s take a look at the content of our nav-bar.module.css file. First, let’s see how composition is made, by using the composes keyword:

.linkButton{...}

.normalLink{...}

.currentRouteLink{
  composes: normalLink;
  font-weight: 300;
}

.standoutLink{
  composes: normalLink linkButton;
  background-color: #33C;
  color: white;
}

As you can see, it’s possible to extend one or multiple classes at a time. The standoutLink class, for example, inherits styles from the normalLink and linkButton classes, besides, it adds its own properties. Just out of curiosity, let’s see the body of the other classes:

.linkButton{
  border-radius: 3px;
  padding: 10px 20px;
}

.normalLink{
  text-decoration: none;
  font-family: arial;
  font-size: 12px;
  color: #33C;
  font-weight: 600;
  display: inline-block;
  margin-right: 1rem;
}

And here’s how this component looks like:
Navbar

It’s also very simple to extend classes declared in other files. This is how you can do it:

.currentRouteLink{
  composes: normalLink from './links.css';
  font-weight: 300;
}

Global classes

You can declare global classes in CSS Modules as well. To do so, just prefix the class name with :global, like this:

:global .title{
  font-size: 20px;
}

.linkButton{...}

.normalLink{...}

.normalLink:hover{...}

The .title class won’t have its name mangled when your component is rendered. This way, you can refer to it normally, as if you were not using CSS modules:

Global classes

Multiple classes with CSS Modules

This is a simple way to use multiple classes in a same element:

<a className={`${styles.normalLink} ${styles.linkButton}`}>...</a>

It just uses template literals to separate class names with a space.

 

Alternatives

CSS Modules are really great on solving naming conflict issues, but treating it as the only solution to this problem is not only wrong, but unfair. There are other great alternatives, some older, some newer, but equally effective when it comes to scoping class names locally. I’ll enumerate some of them:

  • BEM: A battle-tested naming convention, used by companies like Google and BBC. If correctly followed throughout an application, it should prevent conflicts between different classes. Its greatest pro, IMHO, is to be basically a methodology, not a library, plugin, framework or other third-party-code-based solution. To be more precise, in the official site you can find some related tooling, but it’s definitely not required in order to use BEM in your project.
  • CSS-in-JS: This concept is relatively recent and really powerful. It’s growing fast in popularity, specially in the React community. The basic idea is to write the CSS of your applications inside JS files. I know, it sounds weird at first, but it leverages the power of JavaScript to add some “magic” to your CSS (making it comparable in terms of features to preprocessors like Sass and Stylus).
  • Shadow DOM: An API that provides total encapsulation of markup and style. It makes it possible to add a hidden and separated DOM to an element. It’s very promising, but its support isn’t yet satisfactory (as of January 2019).

 

Conclusion

  • By default, in a CSS module, every class name and animation is scoped locally to the component that is importing it. This way, developers can use any naming convention they want, without worrying about global conflicts.
  • CSS Modules also allow class composition. A given class A can inherit from another class B, extending it. It’s even possible to extend multiple classes at the same time.
  • If you’re using create-react-app v2+, now you can have CSS Modules with no need to eject your application or doing some kind of black magic.
  • CSS Modules are not the only solution to naming conflicts. There are other good ways of preventing this kind of problem, such as following a naming convention like BEM, using a CSS-in-JS library (e.g. styled-components), or even leveraging Shadow DOM (when available).

And that’s all. Do you want to become a React master? If so, check out Mosh’s React course. It’s the best React course out there! And if you liked this article, share it with others as well!

 

JavaScript hacker, front-end engineer and F/OSS lover.
Tags: ,

One response to “Using CSS Modules in React”

  1. Hi.

    Is it possible to work with this approach in a nextjs project that uses stylus?

Leave a Reply

Connect with Me
  • Categories
  • Popular Posts