Skip to main content

CSS Modules

Description

A CSS Module is a CSS file where class names are locally scoped. Though the name may make CSS Modules sound like an official spec that extends the CSS language, this is not the case — CSS Modules were invented by members of the community and must be compiled to normal CSS before being served on the web.

To begin using CSS Modules, create a file with the .module.css extension and add a class name selector:

App.module.css
.appContainer {
background-color: #fafafa;
font-family: sans-serif;
}

Then import the CSS module in your JavaScript code and attach the class name to an element:

App.jsx
import styles from './App.module.css';

export function App() {
return <div className={styles.appContainer}>Content here</div>;
}

Some things to note about the above example:

  • We used a camelCase class name — camel case is preferable when using CSS Modules since the class names need to be accessed from JavaScript.
  • The CSS Module for App.jsx is called App.module.css. This is a common pattern when using CSS Modules: for each [component].jsx, you create a [component].module.css file that contains the styles for that component.

Purpose and Use Cases

A common pitfall when using plain CSS (with or without a preprocessor) is that same class name may be accidentally used for two different purposes. For example, imagine page1.css defines styles for an error alert like this:

page1.css
.error {
display: block;
padding: 1rem;
background-color: #faf38f;
border-color: 1px solid red;
font-weight: bold;
}

You are working on page 2 and wish to add a small bottom margin to an error label. If you don't remember that .error styles are also defined in page1.css, you may accidentally reuse the error class name and write something like this:

page2.css
.error {
margin-bottom: 0.5rem;
}

If page1.css and page2.css are compiled into a single CSS bundle which is included in the <head> of your single page app's HTML, page 2's simple error label will end up with a large padding, a background color, and a border, since these styles were defined in page1.css. That isn't what we wanted! We really want page1.css to only apply to page 1, and page2.css to only apply to page 2.

CSS Modules fixes this problem by making all class names locally scoped. It does this by transforming your class names at build time. In the example above, the module loader would transform the class names like this:

error (from page1.css) ➙ _src_page1_module__error
error (from page2.css) ➙ _src_page2_module__error

While the exact transformation algorithm used may vary, the end result is the same: there will never be a collision between two CSS classes defined in different files.

When you import a CSS Module from a JavaScript file, you get an object which maps the human-readable class names to transformed class names. For example, this code,

import styles from 'page1.module.css';
console.log(styles);

will print

{ error: '_src_page1_module__error' }

to the console.

Tradeoffs

There are no major tradeoffs when using CSS Modules instead of plain CSS, though you need to make sure you are using a bundler which supports CSS Modules. CSS Modules are supported out of the box when using Webpack's css-loader, Create React App, or Next.js.

CSS Modules are potentially less convenient than plain CSS if you want to attach styles to the same class name in different files. You can achieve this behavior by using the :global directive as described here.

CSS Modules can be seen as a middle ground between plain CSS and a full-fledged CSS-in-JS library. Like plain CSS, CSS Modules have no runtime overhead since they are transformed to plain CSS at compile time. Like CSS-in-JS libraries, CSS Modules offer locally-scoped styles. That said, CSS-in-JS provides tighter integration between your CSS and JavaScript code:

  • With CSS-in-JS, you can define a React component and its styles in a single .jsx file, whereas with CSS Modules, you need both a .jsx file and a .module.css file.
  • Styles defined in via CSS-in-JS have full access to JavaScript variables (e.g. color constants), while CSS Modules do not.

When Should I Consider Using This?

  • You are working on a medium or large application, since class name collisions are more likely to occur in larger codebases.
  • You want locally-scoped class names, without the larger bundle size and runtime performance costs that come with using a CSS-in-JS library.
  • You are using a CSS preprocessor like Sass. Preprocessors and CSS modules can be used together.

Further Information

Example

This example shows how to create the following component using React and CSS Modules.

A card component with a button

In your .jsx file, import from the .module.css file, which we'll add in a bit. Then write the JSX code and use the styles object when setting the className for each element.

Card.jsx
import styles from './Card.module.css';

export function Card() {
return (
<div className={styles.card}>
<div className={styles.cardContent}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</div>
<button>OK</button>
</div>
);
}

Next, we add the CSS Module:

Card.module.css
.card {
border: 1px solid #ccc;
border-radius: 0.5rem;
padding: 1rem;
max-width: 200px;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}

.card .cardContent {
margin-bottom: 1rem;
}

button {
border: 0;
background-color: #0d6efd;
color: white;
padding: 0.5rem;
font-size: 1rem;
border-radius: 0.25rem;
width: 100%;
}

button:hover {
background-color: #025ce3;
}

And that's it! If properly configured, your bundler will transform the class names in the CSS Module so that your styles are locally scoped.