React allows specifying the style of an HTML element from within the app, which is a powerful tool when the style depends on the data or logic of the app. On the other hand, CSS variables, also known as CSS custom properties, provide great flexibility for CSS.

For example, the following code specifies a CSS variable of the h1 element:

export default function App() {
  return <h1 style={{ "--component-style": "classical" }}>Hello World!</h1>;
}

However, the TypeScript compiler will complain:

src/App.tsx:2:23 - error TS2353: Object literal may only specify known properties, and '"--component-style"' does not exist in type 'Properties<string | number, string & {}>'.

2   return <h1 style={{ "--component-style": "classical" }}>Hello World!</h1>
                        ~~~~~~~~~~~~~~~~~~~

  node_modules/@types/react/index.d.ts:2908:9
    2908         style?: CSSProperties | undefined;
                 ~~~~~
    The expected type comes from property 'style' which is declared here on type 'DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>'

In this post, we discuss how to properly type CSS variables in React. Here, we avoid using type assertions and strive for type safety by typing as narrowly as possible.

Solution 1: Explicitly Spell Out CSS Variable Names

To make the TypeScript compiler recognize CSS variables, we can explicitly spell them out, like the following:

declare module "react" {
  interface CSSProperties {
    "--component-style"?: "classical" | "modern";
  }
}

Then the TypeScript compiler will compile the code snippet at the beginning of this post. Explicitly spelling out the CSS variable names has the advantage of maximized type safety: The TypeScript compiler will catch any typos in the CSS variable name.

Additional Types

If the CSS variable accepts a different type such as any string, we can replace "classical" | "modern" with string. If the CSS variable accepts a special CSS data type, such as color, we can use the corresponding type defined in the CSSType package, like the following:

import { DataType } from "csstype";

declare module "react" {
  interface CSSProperties {
    "--component-ambient-color"?: DataType.Color;
  }
}

export default function App() {
  return <h1 style={{ "--component-ambient-color": "red" }}>Hello World!</h1>;
}

Also check out this explanation/warning on using types from the CSSType package.

Solution 2: Use Template Literal Types

In some apps, there are complex style maneuvers that create CSS variables on the fly. In this case, Solution 1 may not work since CSS variables must be defined during compile time. Instead, we can type them more generally using template literal types.

For example, let us consider an app that creates --component-*-color CSS variables on the fly, where * can be any words, e.g., those defined by users. Then, the following module declaration should work:

import { DataType } from "csstype";

declare module "react" {
  interface CSSProperties {
    [key: `--component-${string}-color`]: DataType.Color;
  }
}

Avoid Typing Overly Broadly

Unless demanded by specific circumstances, we recommend against a general solution such as the following:

declare module "react" {
  interface CSSProperties {
    [key: `--${string}`]: string | number;
  }
}

While solutions like this indeed pass the checks from the TypeScript compiler, they are overly permissive and open the gate for mistakes, such as typos in the CSS variable names. We recommend sticking to Solutions 1 and 2 if possible, and typing as narrowly as possible.

Wanna get the most out of TypeScript? Check out Essential TypeScript 5, Third Edition by Adam Freeman! (affiliate link)
Essential TypeScript Book Cover