How to Use CSS Modules with Create React App

Short answer: you can’t. You gotta ⏏

📎 ⏏ 💿

Facebook’s Create React App is a great way to get started on a new React project quickly. It supports a lot of sensible and popular defaults. But if you want to add unsupported functionality — a CSS preprocessor step like CSS Modules for example—you’re going to have to eject and head out on your own.

This article will explain how to use CSS Modules in a Create React App … app.

Starting a new project with Create React App (I’m just going to call it CRA for short) means you no longer have to manage a bunch of node modules and confusing Webpack configs. After installing CRA globally ($ yarn global add create-react-app) starting a new project is as easy as:

$ create-react-app myApp
$ cd myApp/
$ yarn start

A nice detail is that CRA keeps itself updated so you shouldn’t ever need to manually update the global create-react-app package.

Go your own way

You get a lot of power from those little commands. Unfortunately, if you want to do anything outside of what CRA provides you’ll have to go your own way.

Rumours are that the `eject` command was almost called `break-the-chain`

the `eject` command ⏏

Create React App comes with a handy little command for those of us that have our own opinions about the ingredients of a great React setup: the eject command:

$ yarn eject

The Create React App README is pretty clear about the pros and cons of ejecting:

Note: this is a one-way operation. Once you eject, you can’t go back!
If you aren’t satisfied with the build tool and configuration choices, you can eject at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except eject will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
You don’t have to ever use eject. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#npm-run-eject

I suggest you read that carefully and decide if you are ready to make the split.

Here’s what happens when you eject

$ yarn eject
Are you sure you want to eject? This action is permanent. [y/N]
> y
Ejecting...
Copying files into /Users/user/src/myApp

This replaces your project’s single dependency on react-scripts with the plethora of packages CRA depends on. It also moves some scripts and configurations into your project. Now you have full control of your project.

Running a Git status on your project after running yarn eject shows the affected files and directories:

$ git status
Changes not staged for commit:
modified: package.json
Untracked files:
config/
scripts/

Commit those changes and you are ready to configure CSS Modules.

CSS Modules

I’m not going to tell you how great CSS Modules are. I assume you’re already enlightened. If not read this. It has the best illustrations:

We no longer have to think this intensely about CSS.

Let’s just hop right in and get CSS Modules installed right?

Installing CSS Modules

Lucky us! It turns out the project generated by ejecting from CRA includes all the node modules we need to get CSS Modules running in Webpack. We don’t need to install any additional dependencies!

Configuring CSS Modules

Find the css-loader section in your Webpack dev config (config/webpack.config.dev.js:186 at the time of writing) and change the options portion as so:

// before
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
// after
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: true,
localIdentName: "[name]__[local]___[hash:base64:5]"

},
},

Do the same in your Webpack production config (config/webpack.config.prod.js:191):

// before
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
minimize: true,
sourceMap: true,
},
},
// after
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: true,
minimize: true,
sourceMap: true,
},
},

Note: you can leave the localIdentName out of your production config and you’ll get the default class name format which is just an ugly [hash:base64].

And … that’s it!

If you run yarn start now you will see that your app is now completely unstyled. Paradoxically that means its working.

Update your classNames to use CSS Modules

Your app is looking so brutalist because now you actually have to use CSS Modules in your components. To get started just open your components, and make a few changes:

  1. Assign imported stylesheets to a local constant import styles from './SomeComponent.css' .
  2. Replaces the className strings in your JSX with object indexes className={styles.myClass}.
  3. CSS Modules don’t allow dashes (“-” ) in class names so you will have to update any classes that use a dash. You’ll have to make this change both in the CSS and in the JSX, but a nice bonus is that your class names will be simpler — .App-header becomes just .header for example.

Here’s what the App component src/App.jsx looks like:

Before CSS Modules:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
export default App;

After CSS Modules:

import React, { Component } from 'react';
import logo from './logo.svg';
import styles from './App.css';
class App extends Component {
render() {
return (
<div className={styles.App}>
<div className={styles.header}>
<img src={logo} className={styles.logo} alt="logo" />
<h2>Welcome to React</h2>
</div>
<p className={styles.intro}>
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
export default App;

You’re on your own now

You’re done! No more Create React App. Now you are free to use whatever CSS Preprocessor your want. You’re a grown up now.

If you aren’t ready to eject yet, I know of at least one other approach that allows you to add CSS Module support without ejecting. But this adds another dependency to your project which goes against the point of sticking with CRA.

I hope this little tip was helpful to you. Or at least made you less afraid to eject. And of course, If you know of a better way to use CSS Modules with Create React App let me know in the comments!

Further reading


July 5th, 2017 — examples updated to use Webpack 3 config syntax (thanks Tom Laker and Annie Pennell)