CSS Modules

A CSS Module is a CSS file that defines class and animation names that are scoped locally by default.

CSS modules let you import your .css file into a JavaScript Object with the CSS definitions as properties. It also lets you use the compose property to extend and modularize style definitions.

CSS modules do not have an official specification nor are they a browser feature. They are part of a compilation process that executes against your project to convert scoped classes and selectors into CSS files that the browser can parse and understand.

Tools such as Webpack are used to perform this compilation process.

PWA Studio supports CSS modules out-of-the-box and using them is the recommended approach for styling components.

Why you should use CSS modules

React lets you split the UI into independent and reusable components, which allows you to update small parts of your UI without refreshing the page.

As React applications grow, the amount of components and layers increases. Simple style changes can have unintended side effects on different parts of a complex page.

CSS modules give you the ability to control your element styles in a more granular way. They allow you to build different layers of styles while building your application using a modular approach. CSS modules make it easy and fast to achieve tasks such as upgrading buttons, headings, grids, etc.

For more information on reusable components and code sharing in front end development see: Block Element Modifier (BEM)

Webpack configuration

Webpack uses the Webpack style-loader and css-loader modules to bundle CSS styles using a configuration that looks like the following:

// webpack configuration
{
    test: /\.css$/,
    use: [
        {
            loader: 'style-loader',
            options: {
                injectType:
                    mode === 'development'
                        ? 'styleTag'
                        : 'singletonStyleTag'
            }
        },
        {
            loader: 'css-loader',
            options: {
                importLoaders: 1,
                localIdentName: '[name]-[local]-[hash:base64:3]',
                modules: true
            }
        }
    ]
},

The following is an explanation of the style-loader configuration:

injectType

Allows to setup how styles will be injected into the DOM.

The following is an explanation of each css-loader configuration:

importLoaders

Tells Webpack how many loaders to apply to imported resources before applying css-loader.

localIdentName

Defines the format of the generated local ident. In this case, it is a combination of the following values:

  • [name] - the component name
  • [local] - the CSS definition name
  • [hash:base64:3] - a random base64 hash of 3 digits

Example: Subtitle-titleHighlighted-rCN

modules

Enables or disables CSS modules.

For more information about Webpack configuration, follow these links to the official documentation:

How it works

The following is a basic example of how styles are used without modules:

/* styles.css */
.heading { color: yellow; background-color: blue; margin: 0 0 1rem; }
.titleHighlighted { padding: 1rem 2rem; text-align: center; }
// subtitle.js
import React, { Component } from 'react';
import "./styles.css";

class Subtitle extends Component {
  render() {
    return (
      <div>
          <h2 className="heading">My Title</h2>
          <h2 className="titleHighlighted">My Title</h2>
      </div>
    );
  }
}

export default Subtitle;

This approach is perfectly valid, but it has several downsides as the amount of components and CSS definitions grows. All your definitions are shared across all components which forces you to reference your elements by its DOM inheritance or create unique definition names.

To avoid this, you can use CSS modules to create a component with locally scoped styles:

// subtitle.js

import React, { Component } from 'react';
import myStyles from './styles.css';

class Subtitle extends Component {
  render() {
    return (
      <div>
          <h2 className={ myStyles.heading }>My Title</h2>
          <h2 className={ myStyles.titleHighlighted }>My Title</h2>
      </div>
    );
  }
}

export default Subtitle;

Creating and composing CSS modules

Any valid .css file can be a CSS module. The difference is that the style definitions in that file are scoped to specific components instead of globally.

The composes property is used in CSS module files to combine local style definitions. The following example creates a CSS module that applies the .heading style definition wherever .titleHighlighted is used.

/** ./styles.css */
.heading {
  color: yellow;
  background-color: blue;
  margin: 0 0 1rem;
}

.titleHighlighted {
  composes: heading;
  padding: 1rem 2rem;
  text-align: center;
}

Composing from another file

By default, composes looks for style definitions in the local file. To reuse a definition from another file, use the from keyword.

The following example composes the heading definition with the baseHeading definition from the default_heading.css file.

/* default_heading.css */
.baseHeading { color: yellow; background-color: blue; margin: 0 0 1rem; }

/* styles.css */
.heading {
  composes: baseHeading from './default_heading.css';
  font-weight: bold;
}

Importing and applying styles

The syntax for importing a CSS module is the same as importing a JavaScript module.

import myStyles from './styles.css';

The style definitions in the CSS module are available as properties of myStyles. They are used as values in an element’s className attribute.

The following example defines a Subtitle component which uses the titleHighlighted style definition:

// ./subtitle.js
import React, { Component } from 'react';
import myStyles from './styles.css';

export Subtitle extends Component
{
    render() {
      return (
          <div>
              <h2 className={ myStyles.titleHighlighted }>My Subtitle</h2>
          </div>
        );
    }
}

export default Subtitle;

This example assigns a style based on component logic:

  render() {
    const { isHighlighted, title } = this.props;

    // we evaluate which class to apply, based on a prop received
    const finalStyle = isHighlighted ? myStyles.titleHighlighted : myStyles.heading;

    return(
      <div className={ finalStyle }>
        <h2>{ title }</h2>
      </div>
    );
  }