Markus Bergh

How I style my components with CSS modules

How I style my components with CSS modules

When I needed to style my React components in my applications, such as those in this portfolio, I started by including them in the JavaScript file directly. By using inline CSS you get away from the problem that all CSS is global.

// components/article/article.js

import React from 'react';

const styles = {
   header: {
     color: '#000',
     fontFamily: 'Open Sans, sans-serif',
     fontWeight: 500,
     fontSize: 22
   },
   body: {
     color: '#111',
     fontSize: 18
   }
};

const Article = ({
  header,
  body
}) => (
  <article>
    <h1 style={styles.header}>
      {header}
    </h1>
    <p style={styles.body}>
      {body}
    </p>
  </article>
);

export default Article;

However after some time I needed to share some styles among multiple components. This together with the need of using pseudo-element and media queries I had to use something else because it did not work with inline styling. There are different solutions to handle this and for me, CSS modules works great.

CSS Modules

A CSS module makes it possible to scope your class names (and animation names) locally, and you put your styles as normal in a CSS file. When you import the CSS module in a JavaScript module, an object is exported with all the mappings from the class names you've chosen.

/* components/article/article.css */

.header {
  color: '#000';
  font-family: 'Open Sans, sans-serif';
  font-weight: 500;
  font-size: 22;
}

.body {
  color: '#111';
  font-size: 18;
}
// components/article/article.js

import React from 'react';

import styles from './article.css';

const Article = ({
  header,
  body
}) => (
  <article>
    <h1 style={styles.header}>
      {header}
    </h1>
    <p style={styles.body}>
      {body}
    </p>
  </article>
);

export default Article;

The class names of .header and .body would be transformed to unique values, such as header__feaxD21 and body__fkb8ca, depending on your build configuration.

Global & local scope

It is possible to set the scope and use them together, so for example if you'd add a class name to the body dynamically, you could use it together with a local selector to specify a rule. For example, on all touch devices I would like the navigation to use less height.

/* components/navigation/navigation.css */

.navigation_bar {
  height: 100px;
}

:global .touch :local .navigation_bar {
  height: 50px;
}

Webpack configuration

When using Webpack (2.x) to build your bundle, you will have to configure the css-loader to enable modules. The setting of localIdentName will be the generated class name in the styles that you write. The style-loader will have the generated styles injected in <style> tags.

// webpack.config.js 

...
  {
    test: /\.css$/,
    use: [
      {
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            modules: true,
            importsLoader: 1,
            localIdentName: '[name]__[local]___[hash:base64:5]'
          }
        }
      }
    ]
  }
...

While this works great in development, I am having a different configuration for production to put all generated CSS in an external file instead, to be loaded in parallel to the JavaScript bundle.

To make this happen I am using extract-text-webpack-plugin and chunkhash in the file name to leverage long-term caching by adding a content-dependent cachebuster.

// webpack.production.config.js

import ExtractTextPlugin from 'extract-text-webpack-plugin';

...
  module: {
    rules: [
      test: /\.css$/,
      use: ExtractTextPlugin.extract({
        fallback: 'style-loader',
        use: 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'
      })
    ]
  },
  plugins: [
    new ExtractTextPlugin('styles.[chunkhash].css')
  ]
...

Conclusion

While there are alternatives to add the support of media queries and other CSS3 features in styling React components, I have chosen to go with CSS modules.

  • Quick & easy to write styles, it's vanilla CSS
  • Works with media queries and pseudo-elements out-of-the-box
  • Debugging is a breeze