Skip to content

CSS Modules

On this course platform, I have a “Sidenote” component, used to add tangential information. You've probably noticed them already, they look like this:

Let's build a simplified version of this component!

Here's a rough sketch, in terms of the props and the markup:

function Sidenote({ title, children }) {
return (
<aside>
<h3>{title}</h3>
<p>{children}</p>
</aside>
);
}

And here's how we style it with CSS Modules:

Code Playground

import styles from './Sidenote.module.css';

function Sidenote({ title, children }) {
return (
<aside className={styles.wrapper}>
<h3 className={styles.title}>{title}</h3>
<p>{children}</p>
</aside>
);
}

export default Sidenote;

There's a lot of weird stuff going on here. I'll explain exactly how this all works, but first, I'd suggest spending a few moments poking at this for yourself!

For example, what do you suppose that styles import is? Make a guess, and then see if you're right by logging it, with console.log(styles).

After you've poked at this example a bit, watch this video for an in-depth explanation:

Video Summary

  • React applications generally need to be compiled, in order to turn JSX into browser-friendly React.createElement function calls.
  • During that same compilation step, we can do other things as well! Modern bundlers like Webpack support importing non-JS files like CSS modules.
  • When our CSS file ends in .module.css, and we import it like a JS module, three things happen:
    1. Longer, guaranteed-unique class names are generated for every CSS class in the module
    2. The raw CSS, using the longer generated class names, are appended to the document's <head>.
    3. A styles object is produced, mapping the short classes onto their generated alternatives.

We wind up with an object like this:

{
wrapper: "_components_Sidenote_module__wrapper",
title: "_components_Sidenote_module__title",
}

The keys (eg. wrapper) are the names we actually wrote in our CSS file. The values (eg. _components_Sidenote_module__wrapper) are the generated, guaranteed-unique values actually used in CSS.

In our JSX, we write:

<aside className={styles.wrapper}>

…Which is equivalent to:

<aside className="_components_Sidenote_module__wrapper">
  • This means that we get all of the benefits of naming methodologies like BEM, but with none of the drawbacks! We don't have to manually come up with clever unique names, we don't pollute our code with gargantuan classes, and we don't need superhuman discipline to remember to use the system. It all happens automatically for us.
  • How does it know that each name will be guaranteed unique? It uses the filesystem! Every CSS class will have the file's full path as a prefix, and because it's impossible for 2 files to exist in the same space, we can be 100% sure that each class will be unique.

You can learn more about Webpack in the Webpack “Tools of the Trade” bonus lesson 👀.