CSS variables with React and TypeScript
Here’s a common issue I bump into when using React and TypeScript. I try to
initialize a CSS variable in my component’s style
prop like so.
function MyComponent() {
return <div style={{ "--my-variable": 0 }}>...</div>
}
But I hit a type error:
Object literal may only specify known properties, and '"--my-variable"' does not exist in type 'Properties<string | number, string & {}>'.
This happens because React uses the Properties
interface from the “csstype”
library to define the allowed properties of the style
prop. This includes lots
of known CSS properties, but it doesn’t know about my custom CSS variables.
If you follow the style
prop’s type definitions with Cmd + Click, you’ll find
this comment in the React code:
export interface CSSProperties extends CSS.Properties<string | number> {
/**
* The index signature was removed to enable closed typing for style
* using CSSType. You're able to use type assertion or module augmentation
* to add properties or an index signature of your own.
*
* For examples and more information, visit:
* https://github.com/frenic/csstype#what-should-i-do-when-i-get-type-errors
*/
}
As it explains, you can work around the error a type assertion like this:
function MyComponent() {
return <div style={{ "--my-variable": 0 } as React.CSSProperties}>...</div>
}
Or you can use module augmentation. I prefer the augmentation approach as it maintains the safety of the original types, but don’t get errors when I decide to use CSS variables.
It doesn’t really matter what you call this augmentation file or where you put
it, all that matters is it’s in a .d.ts
file that TypeScript can see (anywhere
alongside your source files is usually included). I usually follow the
convention that Vite and Next.js use for vite-env.d.ts
and next-env.d.ts
by
calling this react-env.d.ts
to signal that I’m augmenting types from React.
You could specify a strict list of allowed CSS variable types:
// react-env.d.ts
import "react"
declare module "react" {
interface CSSProperties {
"--my-variable"?: number
}
}
Or you can use template literal type to allow arbitrary CSS variables:
// react-env.d.ts
import "react"
declare module "react" {
interface CSSProperties {
[key: `--${string}`]: string | number | undefined
}
}
Now you’ll continue to get errors if you typo a regular style property, but
properties prefixed with --
will be allowed with the values of your choosing.
For me, the template literal approach is a good balance of safety and flexibility.