DEV Community

Cover image for Leveraging the Power of CSS Modules and the "Classnames" Package in React
Alfonsina Lizardo
Alfonsina Lizardo

Posted on

Leveraging the Power of CSS Modules and the "Classnames" Package in React

Table of Contents

Introduction

As software developers, we constantly strive to build maintainable code, and using CSS modules and the “classnames” package for styling your React application is a great way to do that.

If you’re a beginner React developer you’ve probably tried using a global stylesheet for your CSS or maybe even sass with the 7-1 pattern or something alike, while that’s certainly an option, we’ll look at the downsides of it and the advantages of using CSS modules and/or the “classnames” package instead, which are both powerful tools that can enhance the development process and improve the organization of styles in React applications.

So in this article, we will explore how to use them to achieve modular and maintainable styling in React.

The issue with using global styles

As mentioned above, one common approach to styling React components is through the use of global stylesheets. While this method may suffice for smaller projects, it becomes challenging to manage as the codebase grows, and that’s when you may encounter some issues.

The main issue has to do with scope and specificity, since global stylesheets can lead to naming conflicts and specificity issues when different components use the same class names.

It may be easy to avoid components using the same class names when the project is small but it becomes a nightmare to maintain when it’s a big project and several developers are working on it. It’s challenging to isolate and manage styles for individual components, leading to unexpected visual discrepancies.

The same applies when using sass with the 7-1 pattern or even having each component along with its styles file in its own folder.

It could happen for example that there’s a component which has a container-box class name, and you’re requested to build some new functionality and you create a component and use the same class name (without knowing the class already exists) in your component with your own styles, depending on the build process either your component styles or the ones for the component that already existed will be overwritten.

That’s when CSS modules come to the rescue ✨

What are CSS Modules and why use them?

CSS modules provide a solution to the problems associated with global stylesheets, it’s a popular approach to scoping CSS styles locally, ensuring encapsulation and eliminating naming conflicts by creating an unique class name for each component, which promotes modularity and makes CSS code much easier to maintain and refactor.

Besides that, by using CSS modules, the styles will be co-located with their corresponding components, which makes it easier for developers to understand and modify styles without worying about affecting other parts of the application.

To use CSS Modules in a React project, you need to set up the necessary build tools, but this tipically is preconfigured out of the box with most of the tools used currently to start a React application, such as Vite, NextJS or Gatsby, even create-react-app (although it’s no longer recommended to use CRA for your React projects), which makes the setup process seamless.

You’re probably thinking by now (I hope 😅) “Ok, enough convincing, how do I use them? 👀” well, fortunately it’s extremely easy!

Let’s say you have a Button.jsx component, in the src/components/Button folder, to use CSS modules, simply create a regular CSS file in the same folder and add .module.css extension to it, for example button.module.css (technically you can call it anything you want but I find it a better practice to name it the same as your component), and it also works with sass/scss, just change .css for .scss/.sass.

Next, add the desired class styles to your CSS file, for example:

.btn {
  cursor: pointer;
  border: none;
}
Enter fullscreen mode Exit fullscreen mode

And then you import the CSS module in your React component and use it as such:

// SwitchButton.jsx
import styles from './button.module.css';

const SwitchButton= () => {
  return <button className={styles.btn}>Click me</button>;
}
Enter fullscreen mode Exit fullscreen mode

That’s it! the build process will replace the CSS class with an unique name similar to this _btn_1411r_23, so if you or another developer, by any chance, add a new .btn class in another component, the build process will add an unique id to it so neither of the styles will be overwritten 🙌🏼

CSS modules also allow you to dynamically assign classes, which is great! but, it also has a bad side, which is that if you need to add multiple classes, it can get pretty wordy, especially if you’re also conditionally adding classes, for example:

// SwitchButton.jsx
import styles from './button.module.css';

const SwitchButton = ({ isDisabled, border, type }) => {
  return (
    <button 
      className={`
        ${styles.btn}
        // Dynamic class
        ${styles[type]}
        // Conditional classes
        ${isDisabled ? styles.isDisabled : ''}
        ${border ? styles.border : ''}
      `}
    >
      Click me
    </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

And that’s where the 'classnames' packages can help us! 🙌🏼

What is the classnames package and how to use it?

The Classnames package is a utility library that provides a convenient way to conditionally apply CSS classes to elements. It simplifies the process of conditionally styling components based on different states or props.

To use the Classnames package, you first need to install it with npm install classnames and then you simply import it in the component where you wish to use it. First, let’s see an example on how to use it by itself, without CSS modules, here’s the previous example, using classnames:

// SwitchButton.jsx with Classnames and without CSS modules
import classNames from 'classnames';
import './button.css';

const SwitchButton = ({ isDisabled, border, type }) => {
  return (
    <button 
      className={classNames('btn', type, {
        'active': isDisabled,
        border
      })}
    >
      Click me
    </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

You can see that we passed a string, then a dynamic class based on the type prop and then an object where we place our conditional classes.

In the object the key represents the name of the class and the value represents the conditional, so in this case the class is “active” and it will only be applied if isActive is true.

You can pass any conditional here, it doesn’t necessarily have to be a variable or a prop, the conditional could be type === 'success' for example. And just like any object, you can also omit the value if the value an the key have the same name, as in the case of “border”.

This is much better right? much less wordy. You might say “of course it’s better, we’re not using CSS modules, it’d be less wordy too if we used regular CSS”, well actually no 😅 you’ll see later that we can get almost the same result using CSS modules, and it’s definitely less wordy than handling the conditional classes with regular CSS:

// SwitchButton.jsx without Classnames and without CSS modules
import './button.css';

export const SwitchButton = ({ isDisabled, border, type }) => {
  return (
    <button 
      className={`
        btn
        ${type} 
        ${isDisabled ? 'active' : ''}
        ${border ? 'border' : ''}
      `}
    >
      Click me
    </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

So handling classes with “classnames” is still a great choice if you’re not using CSS modules, but using only the package does not solve the original issue of class naming conflicts, so let’s see how we can implement “classnames” along with CSS Modules and get the best of both worlds ✨

Classnames and CSS Modules Integration

There are 2 ways in which we can do this, I find the first one much better than the second, but we’ll see both in case you can’t use the first one for whatever reason.

1. Bind version

Let’s see the code first:

import styles from "./button.module.css";
// import from classnames/bind
import classNames from 'classnames/bind'

// bind the styles to classNames
const cx = classNames.bind(styles);

export const SwitchButton = ({ isDisabled, border, type }) => {
  return (
    <button
      /* use them almost as if you were using */
      /* a regular/global CSS stylesheet */
      className={cx('btn', [type], {
        'active': isDisabled,
        border
      })}
    >
      Click me
    </button>
  );
};
Enter fullscreen mode Exit fullscreen mode

This is what I meant by getting almost the same result by using classnames + CSS modules as if we were using global styles. As you can see here, there are only some few changes that we need to do (as stated in the comments in the code), but the end result is great, much more concise than using only CSS modules.

2. No Bind version

Again, let’s see the code first:

import styles from "./button.module.css";
// import classNames as if not using CSS modules
import classNames from 'classnames' 

export const SwitchButton = ({ isDisabled, border, type }) => {
  return (
    <button
      /* use the classes by accessing them through */
      /* the styles object */
      className={classNames(styles.btn, [styles.type], {
        [styles.active]: isDisabled,
        [styles.border]: border
      })}
    >
      Click me
    </button>
  );
};
Enter fullscreen mode Exit fullscreen mode

You probably can see why I prefer the previous way, this one is less concise since we need to apply the styles by accessing the “styles” object, as we first saw when using CSS modules, but I’ve worked in some projects where the first way hasn’t worked for me (probably due to environment or compiling configurations) so I’ve had to settle with the second 😅

Conclusion

In this article, we have explored the usage of CSS Modules and the Classnames package in React. We have seen how CSS Modules provide local scoping and better organization of styles, while the Classnames package simplifies conditional styling. By leveraging these tools, you can enhance readability, maintainability, and scalability, allowing for a better development experience.

I hope the article was useful for you 🙌🏼 and if you have any questions please feel free to leave them in the comments below. Happy coding! 🤘🏼

Top comments (2)

Collapse
 
hossain45 profile image
Hossain MD Athar

thanks. i was looking for something like this

Collapse
 
alais29dev profile image
Alfonsina Lizardo

Glad I could help! :D (I'm sorry for the super late reply xD)