CSS Modules for React and Typescript

Tommy Bernaciak
Apr 10, 2018 · 5 min read

React is great and everybody knows it. However, everybody new to this technology may have some concerns. One thing I found out really strange in the very beginning was a concept of keeping styles along with component’s code in the same file. I know this is not the only way of doing it, but this is the one you will see in the examples for beginners. You can still use good and old CSS. Some time ago I needed to investigate a bit more to find a solution that will give me the best of both world’s: styles that are easy to create and maintain and scoped locally, not visible outside a particular component file.

Inline styles

A typical React component looks like the one below. It is just a button that will doSomething when clicked.

import * as React from 'react';export class MyComponent extends React.Component<IComponentProps> {
render() {
const style = {
margin: '0.5em',
padding: 0,
};
const btnStyle = {
color: red,
border: '1px solid red'
};
return (
<div style={style}>
<button
type="button"
style={btnStyle}
onClick={() => this.doSomething()}
>
do something
</button>
</div>
);
}
doSomething(): void {
...
}
}
interface IComponentProps {
itemId: number;
value: string;
}

What I really don’t like in here is styling. The general concept of JS variable that holds component style is interesting, but when you component gets bigger and needs a bit more sophisticated view this code is really hard to read. Sometimes component file becomes disproportionately big compared to what it is really doing.This is also very difficult to obtain some effect, hover for example. What could be done in one CSS line, here you need to add a new action to Redux for HoverToggle. Generally, I also don’t like keeping CSS, JS and HMTL in one file, but this is just because I am not used to writing code like this. However, I understand many advantages of this, a true code separation for example.

You can find in React docs that using inline styling is not recommended and that CSS classes should be used instead. But when you create a component that will be used on another website there is a risk that you will overwrite existing stylesheet.

CSS Modules

I tried couple different solutions for this problem and I can really recommend CSS Modules. This allows you to have CSS classes that are scoped locally by default. Stylesheet is scoped to this one particular file so there is no risk of overwriting different elements style. You can write your style code in regular CSS stylesheet file and use everything you know, including hovers or media queries. Some behaviour just can’t be obtained simpler in another way.

Using CSS Modules is very easy, but when you are using Typescript then you need to add typings to your setup.

yarn add typings-for-css-modules-loader — dev

Then change your Webpack configuration:

plugins: [
...
new webpack.WatchIgnorePlugin([/css\.d\.ts$/])
]
...test: /\.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('typings-for-css-modules-loader'),
options: {
modules: true,
importLoaders: 1,
localIdentName: '[name]__[local]___[hash:base64:5]',
namedExport: true,
camelCase: true
},
},
...

If you were using css-loader you can easily change to typings-for-css-modules-loader. It accepts the same options so it will work the same for you. It allows CSS Modules you should set modules option and camelCase option, which will make you development a bit more comfortable. You can also configure your class names to always be unique. localIdentName: ‘[name]__[local]___[hash:base64:5]’ will build CSS class name as {ComponentName}__{cssClass}__{random 5 letter string}.

It allows you to use everything great from CSS you cannot use with inline styling. Look at CSS code below, it can be an example CSS file for your main component. Now you can style your component using all CSS features like variables or hovering.

Component.css:

:root {
--fontSize: 10px;
--grey: #f2f2f2;
--border: 1px solid #f6f8fa;
--borderInvalid: 2px solid #ff0000;
--colorHover: #f3f3f3;
}
.general {
position: relative;
font-size: var(--fontSize);
text-transform: capitalize;
box-sizing: border-box;
margin: 0;
border: 0;
background: var(--grey);
}
.component {
composes: general;
border-right: var(--border);
height: 100%;
}
.component[data-invalid=true] {
border-bottom: var(--borderInvalid);
}
.button {
composes: general;
cursor: pointer;
}
.button:hover {color: var(--colorHover);}
.button:focus {outline:0;}

If you are using TypeScript you need typings, but they are automatically generated with plugin added in the very beginning. You don’t need to think about it.

Component.css.d.ts:

export const root: string;
export const general: string;
export const component: string;
export const button: string;

Then you can easily use created stylesheet by simply importing this file in your component’s code.

import * as styles from ‘./Component.css’;

This is cool, huh?

What next?

Well, there is still a rick that you will overwrite CSS variables so it would be good to separate them. A small postCSS plugin will help. PostCSS CSS Variables allows you to have a config file with variables that will be transformed to a final code. Just install it:

yarn install postcss-css-variables

Now you can create a separate file, let’s name it ./cssVariables. Based on a previous example it will look like this:

const variables = {
'grey': '#f2f2f2',
'border': '1px solid #f6f8fa',
'borderInvalid': '2px solid #ff0000',
'fontSize': '10px',
'colorHover': '#f3f3f3'
};
module.exports = variables;

You need also to modify your Webpack configuration. You only need to load the plugin and specify the file with defined variables.

const cssVariablesPlugin = require('postcss-css-variables');
const cssVariables = require('./cssVariables');
...{
test: /\.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('typings-for-css-modules-loader'),
options: {
modules: true,
importLoaders: 1,
localIdentName: '[name]__[local]___[hash:base64:5]',
namedExport: true,
camelCase: true
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
cssVariablesPlugin({
variables: cssVariables
})

],
},
}
}

Now you need to restart your server and you should see that everything is working correctly. Variables, however, are no longer included in :root element. Variables are replaced with values during compilation so you don’t need to care about overwriting :root element.

I think this is a very interesting solution for using CSS Modules in React development. If you have any comments, please leave them below.

Tommy Bernaciak

Written by

Superhero, Full Stack Developer @BinarApps // tommybernaciak.com

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade