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.