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.
⇣ Quick access ⇣
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 { color: purple}
and use it (the wrong way) in a component like this:
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 { 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.
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 { 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.
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.