Learn React 18: Using CSS Modules

It is possible to use React without any toolchain and integrate it directly into your existing website by using simple script tags. However, using toolchains will speed up your development, give you access to some additional features, and allow you to detect mistakes in your code.

One popular tool for building new single page apps is Create React App. You can get started with it by running a single command. Here, rgen-app is simply the name of your app.

1
npx create-react-app rgen-app

This will install a bunch of packages and tools for you like webpack, Babel, ESLint etc. The feature of Create React App that we will discuss in this tutorial is the ability to use CSS modules.

What are CSS Modules?

CSS rules have global scope. This basically means that any CSS rules that you define will apply to all elements with the same class. This is indeed what we want to happen. Defining the same rules again and again would be very cumbersome.

However, things can get tricky when we are working on large projects. One problem would be name collisions. For example, a lot of elements in your project might require wrapper div tags around them for proper styling. However, you would probably want these wrappers to have different values for properties like margin and padding etc. This will require you to write CSS that uniquely targets these wrappers. It will get hard for you to keep track of all the unique classnames or specific CSS selectors and avoid any collisions.

For another example, consider two developers who are working on two different independent components for a larger project. They might use same classnames for some of the elements which could ultimately result in conflict due to the global scoping of CSS.

CSS modules will help you solve these problems. They make sure that your CSS is locally scoped by adding a unique hash to your classnames. You will understand it better once we use CSS modules for styling our random number generator app.

Lets get started.

Setting Up the Project

Execute the following command in a directory of your choice to install everything needed for setting up the Create React App tool .

1
npx create-react-app rgen-app

This will create a bunch of files in different directories. However, we are concerned only with the src directory for now. Delete all the files in the directory and create three new files named index.js, RandomGenerator.js and RandomGenerator.css.

Place the following code inside the index.js file:

1
import React from 'react';
2
import ReactDOM from 'react-dom/client';
3
import RandomGenerator from './RandomGenerator';
4

5
const root = ReactDOM.createRoot(document.getElementById('root'));
6
root.render(
7
  <RandomGenerator low={500} numRange={1000} />
8
);

Place the following code inside the RandomGenerator.js file:

1
import React from 'react';
2
import './RandomGenerator.css';
3

4
class RandomGenerator extends React.Component {
5
    constructor(props) {
6
      super(props);
7
      
8
      this.rangeInput = React.createRef();
9
      this.lowInput = React.createRef();
10
  
11
      this.state = {
12
        numberRange: props.numRange,
13
        lowerLimit: props.low,
14
        randomNumber: Math.floor(Math.random() * props.numRange) + props.low
15
      };
16
      
17
      this.handleSubmission = this.handleSubmission.bind(this);
18
    }
19
    
20
    handleSubmission(e) {
21
      e.preventDefault();
22
      
23
      let newRange = parseInt(this.rangeInput.current.value);
24
      let newLow = parseInt(this.lowInput.current.value);
25
  
26
      
27
      if(isNaN(newRange) || newRange < 0) {
28
        newRange = 0;
29
      }
30
      
31
      if(isNaN(newLow) || newLow < 0) {
32
        newLow = 0;
33
      }
34
      
35
      this.setState({numberRange: newRange, lowerLimit: newLow});
36
      
37
      this.lowInput.current.focus();
38
    }
39
  
40
    componentDidMount() {
41
      this.numTimer = setInterval(() => {
42
        let tempVal = Math.floor(Math.random() * this.state.numberRange) + this.state.lowerLimit;
43
        this.setState({ randomNumber: tempVal });
44
      }, 2000);
45
    }
46
  
47
    componentWillUnmount() {
48
      clearInterval(this.numTimer);
49
    }
50
  
51
    render() {
52
      return (
53
        <div class="container">
54
          <h1>Random Number Generator</h1>
55
          <div className="range-container">
56
          <p className="capitalized">Lower Limit: {this.state.lowerLimit}</p>
57
          <p className="capitalized">Upper Limit: {this.state.lowerLimit + this.state.numberRange}</p>
58
          </div>
59
          <h2 className="random-number">{this.state.randomNumber}</h2>
60
          <form>
61
            <input type="number" ref={this.lowInput} />
62
            <input type="number" ref={this.rangeInput} />
63
            <button type="submit" onClick={this.handleSubmission}>Submit</button>
64
          </form>
65
        </div>
66
      );
67
    }
68
}
69

70
export default RandomGenerator;

Place the following code inside the RandomGenerator.css file:

1
body {
2
    margin: 20px auto;
3
    font-family: 'Lato';
4
    font-weight: 300;
5
    text-align: center;
6
}
7

8
h1 {
9
    font-family: 'Aclonica';
10
    font-size: 3.5rem;
11
}
12

13
.random-number {
14
    font-size: 4rem;
15
    background: orangered;
16
    color: white;
17
    width: fit-content;
18
    display: inline-block;
19
    padding: 0 1rem;
20
    border-radius: 10px;
21
}
22

23
.capitalized {
24
    text-transform: uppercase;
25
    font-weight: bold;
26
}
27

28
form {
29
    display: flex;
30
    gap: 2rem;
31
    margin: 0 auto;
32
    justify-content: center;
33
    flex-direction: column;
34
    width: 80%;
35
}
36

37
input {
38
    border: none;
39
    border-bottom: 2px solid black;
40
    font-size: 2rem;
41
    font-family: 'Lato';
42
    outline: none;
43
}
44

45
input:focus {
46
    border-bottom: 2px solid blue;
47
}
48

49
button {
50
    border: 2px solid black;
51
    font-size: 2rem;
52
    padding: 0.5rem 2rem;
53
    font-family: 'Lato';
54
    text-transform: uppercase;
55
    font-weight: bold;
56
    cursor: pointer;
57
}
58

59
.range-container {
60
    display: flex;
61
    gap: 4rem;
62
    justify-content: center;
63
}
64

65
.range-container p {
66
    font-size: 1.5rem;
67
}

The output of the code above should be similar to the following image:

Create React App Visual AppearanceCreate React App Visual AppearanceCreate React App Visual Appearance

The markup generated by the app would look as shown below:

Create React App Old MarkupCreate React App Old MarkupCreate React App Old Markup

Using CSS Modules

You only have to make small changes to the files RandomGenerator.js and RandomGenerator.css to start using CSS modules.

Lets begin with the CSS file. First, rename it to RandomGenerator.module.css. Now, convert your classnames that are in kebab-case to camelCase. For example, range-container becomes rangeContainer and random-number becomes randomNumber. The CSS file would be updated to have the following rules:

1
.randomNumber {
2
    font-size: 4rem;
3
    background: orangered;
4
    color: white;
5
    width: fit-content;
6
    display: inline-block;
7
    padding: 0 1rem;
8
    border-radius: 10px;
9
}
10

11
.rangeContainer {
12
    display: flex;
13
    gap: 4rem;
14
    justify-content: center;
15
}
16

17
.rangeContainer p {
18
    font-size: 1.5rem;
19
}

When modifying the RandomGenerator.js file, we will begin by updating the import statement for our CSS:

1
import './RandomGenerator.css';

will become:

1
import styles from './RandomGenerator.module.css';

Using styles is a convention here and you can use some other word if you like. This styles object will give you access to the class selectors from the CSS file.

Modify the render() method of the component so that it looks as shown below:

1
render() {
2
  return (
3
    <div class="container">
4
      <h1>Random Number Generator</h1>
5
      <div className={styles.rangeContainer}>
6
      <p className={styles.capitalized}>Lower Limit: {this.state.lowerLimit}</p>
7
      <p className={styles.capitalized}>Upper Limit: {this.state.lowerLimit + this.state.numberRange}</p>
8
      </div>
9
      <h2 className={styles.randomNumber}>{this.state.randomNumber}</h2>
10
      <form>
11
        <input type="number" ref={this.lowInput} />
12
        <input type="number" ref={this.rangeInput} />
13
        <button type="submit" onClick={this.handleSubmission}>Submit</button>
14
      </form>
15
    </div>
16
  );
17
}

Visually, our random number generator app will look exactly the same but the classnames attached to different elements would have changed. Here is what the new markup would look like:

Create React App New MarkupCreate React App New MarkupCreate React App New Markup

Did you notice that React prepended and appended the classname with the component name and a hash respectively? This way it makes sure that there are no name collisions in selectors.

Final Thoughts

Using CSS modules when developing your React app means that you no longer have to worry about new style rules messing up your layout. You can also use them in conjunction with regular CSS to get the best of both worlds.