Catching issues as early as possible is essential for the developer experience. But browsers and many popular tools will happily ignore invalid properties, usage of non-existing classes, etc. Let’s explore how you can improve that and create a better developer experience for your project.

tl;dr?


CSS-in-JS

The CSS-in-JS approach tends to be typesafe by default.

// This is type-safe CSS. It's a CSS object that you can use in your components.
const helloWorldStyle: React.CSSProperties = {
  color: "purple",
};

export function HelloWorld() {
  return <div style={helloWorldStyle}> Hello world! </div>;
}
export function HelloWorld() {
  return (
    <div
      style={{
        // This is also type-safe CSS.
        color: "purple",
      }}>
      Hello world!
    </div>
  );
}

CSS modules

CSS modules are regular CSS files with limited scope. However, they lack types when used in a component. It becomes a problem when classes are renamed or removed.
You could have a CSS module like this:

/* πŸ“‚/HelloWorld.module.css */
.helloWorld {
    color: purple
}

and use it (the wrong way) in a component like this:

// πŸ“‚/HelloWorld.tsx
import styles from "./HelloWorld.module.css";
export function HelloWorld() {
  return <div className={styles.helloWorld123}>Hello world!</div>;
}

The example uses wrong classname, but our IDE, the browser and all other tooling stays silent. One of the ways to get a validation for the class names is to generate types with the help of typed-css-modules and get autocomplete in our IDE with typescript-plugin-css-modules. Because these are CLI tools, I like to use npm-run-all and run them at development time.
Start by installing the dependencies:

$ npm install -D typed-css-modules typescript-plugin-css-modules npm-run-all

Next, update the scripts in your package.json file to start the development server and css modules watcher at the same time.

"scripts": {
   "dev:start": "vite",
   "dev:css": "tcm src --watch",
   "dev": "run-p dev:**",
 }

TCM will run in the background and automatically generate types when it detects changes in the CSS module files. This is the generated types file from my CSS modules example:

declare const styles: {
  readonly "helloWorld": string;
};
export = styles;

To enable autocomplete in your IDE add css modules plugin to tsconfig.json.

{
  "compilerOptions": {
    "plugins": [{ "name": "typescript-plugin-css-modules" }]
  }
}

Sass/SCSS

For SCSS we can use typed-scss-modules for type generation and npm-run-all to generate them during development time.
Start by installing the dependencies:

$ npm install -D typed-scss-modules npm-run-all

Next, update the scripts in your package.json file to start the development server and scss files watcher at the same time.

"scripts": {
   "dev:start": "vite",
   "dev:scss": "typed-scss-modules src --watch --exportType default",
   "dev": "run-p dev:**",
 }

This is my sample scss file:

// πŸ“‚/HelloWorld.scss
.helloWorld {
    color: purple;
  }

and these are the generated types:

export type Styles = {
  'helloWorld': string;
};

export type ClassNames = keyof Styles;

declare const styles: Styles;

export default styles;

When used in a component, you will get autocomplete for free.

// πŸ“‚/HelloWorld.tsx
import styles from "./HelloWorld.scss";
export function HelloWorld() {
  return <div className={styles.helloWorld}>Hello world!</div>;
}

LESS

When it comes to LESS we can use typed-less-modules to generate the types and npm-run-all to keep the file watcher running during development time.
Start by installing the dependencies:

$ npm install -D typed-less-modules npm-run-all

Next, update the scripts in your package.json file to start the development server and LESS files watcher at the same time.

"scripts": {
   "dev:start": "vite",
    "dev:less": "tlm src --watch --exportType default",
   "dev": "run-p dev:**",
 }

This is my sample LESS file:

// πŸ“‚/HelloWorld.less
.helloWorld {
    color: purple
}

and these are the generated types:

export interface Styles {
  'helloWorld': string;
}

export type ClassNames = keyof Styles;

declare const styles: Styles;

export default styles;

When used in a component, you will get autocomplete for free.

// πŸ“‚/HelloWorld.tsx
import styles from "./HelloWorld.less";
export function HelloWorld() {
  return <div className={styles.helloWorld}>Hello world!</div>;
}

Next steps

If your project doesn’t have types for the CSS classes, now is the time to add them! It takes less than 15 minutes to implement and they go a long way in preventing all of the silly mistakes in the next code refactor.