2 Comments

Increasing the Value of CSS Modules with Typescript

There are many methods for managing styles in web applications, all of which attempt to prevent styles from cascading to elements that don’t need them. CSS Modules are an excellent tool for isolating application styles to the components that use them. And with the power of Typescript, we can make them even better.

Typescript support for CSS Modules

I’ve put together an example repository, but here are the steps to set it up for yourself.

1. Create a New Project

The easiest way to try out CSS Modules on a real application is to create a new one. Create React App has a template for Typescript, which is why I used it in the example repository. If you already have a project set up for this, you can skip this step.

To create a new React project with Typescript, run the following command:

create-react-app project-name --template typescript

2. Rename CSS Files

CSS Modules are typically indicated by the filename *.module.css, but can also be *.module.less or *.module.scss, depending on the CSS preprocessor you’re using. The Create React App project already supports CSS Modules by default for any file that matches these file extensions, so rename your *.css files to *.module.css.

If you’re not using Create React App, you might need to mess with the webpack config or similar build system config to make this work.

3. Import CSS Modules

Now it’s time to use CSS Modules in the React components. This is what a React component might look like before using CSS Modules:


import "./styles.css";

const HelloWorld = () => {
    return <h1 className="helloStyle">Hello World!</h1>;
}

export default HelloWorld;

The styles are imported into the component, which ensures that the class name "helloStyle" is available when this component is rendered. It’s important to note that the classes in ./styles.css are available to every component once this component imports them.

The styles aren’t isolated to this specific component, which means there’s still the possibility of class name collisions. There’s also nothing to help with spelling errors for class names. I can’t even count how many times I’ve traced styling issues to a misspelled class name.

With CSS Modules, the code is mostly the same, but now we can import the styles as a module that we can interact with:


import styles from "./styles.module.css";

const HelloWorld = () => {
    return <h1 className={styles.helloStyle}>Hello World!</h1>;
}

export default HelloWorld;

In this example, the styles import is a Javascript object that we can use to apply styles where needed. The "helloStyle" class name is now a property on the styles object that can be accessed like any other property on a Javascript object.

CSS Modules also help with class name collisions by auto-generating unique class names for each property on the styles object. If you were to inspect this component in the browser, you would most likely see a class name like "styles_helloStyle_7a4w" instead of "helloStyle".

Unfortunately, we still don’t have help when it comes to spelling errors. Since the styles object is just a plain Javascript object, we don’t have a way to check if the desired property exists on the object until run-time. This is where Typescript can help us!

4. Add Typescript Plugin

Since the project already uses Typescript, we can utilize it to help declare the types of our styles object. You could do this yourself by creating a declaration file for each of our *.module.css files, but that’s a lot of extra work just to check for spelling errors.

Instead of doing that, you should install the typescript-plugin-css-modules Typescript plugin as a devDependency, like so:

npm install typescript-plugin-css-modules --save-dev

Or if you’re using Yarn, run:

yarn add -D typescript-plugin-css-modules

You still need to define a declaration file for your CSS Modules. (The Create React App project should already have support for this.) Create a new file at the root of the project called styles.d.ts (or something similar), and add the following code to it:


// For CSS
declare module "*.module.css" {
  const classes: { [key: string]: string };
  export default classes;
}

// For LESS
declare module "*.module.less" {
  const classes: { [key: string]: string };
  export default classes;
}

// For SCSS
declare module "*.module.scss" {
  const classes: { [key: string]: string };
  export default classes;
}

If you were to misspell a property name on the styles object now, you would get a compile-time error. This is excellent!

5. Use Workspace Version of Typescript

VS Code includes its own version of Typescript, which can be helpful in some situations. Unfortunately, the typescript-plugin-css-modules plugin won’t work with the VS Code version of Typescript. Your project should already depend on Typescript as a devDependency, so we need to configure VS Code to use that workspace version instead.

Create a directory named .vscode at the root of the project (if it doesn’t already exist), and add a new file called settings.json with the following settings:


{
    "typescript.tsdk": "node_modules/typescript/lib",
    "typescript.enablePromptUseWorkspaceTsdk": true
}

This will ensure that VS Code will use the project’s version of Typescript instead of the VS Code version and will prompt you to do so if you aren’t already.


I hope you found this post helpful and can start using CSS Modules to their fullest potential with the help of Typescript!