249

I want to define jsx like this:

<table style={{'--length': array.length}}>
   <tbody>
      <tr>{array}</tr>
   </tbody>
</table>

and I use --length in CSS, I also have cells that have --count that shows count using CSS pseudo selector (using the counter hack).

but typescript throws an error:

TS2326: Types of property 'style' are incompatible.
  Type '{ '--length': number; }' is not assignable to type 'CSSProperties'.
    Object literal may only specify known properties, and ''--length'' does not exist in type 'CSSProperties'.

is it possible to change the type of style attribute to accept CSS variables (custom properties) or is there a way to force any on the style object?

4
  • i believe this is discussed here github.com/facebook/react/issues/6411 Commented Aug 24, 2018 at 13:27
  • @SGhaleb I've seen this, in my code css variables works they appear in DOM and css is applied, but they give error in webpack (it look like error but compile pass) when building the app, so it's the problem with typescript typings not with react. Commented Aug 24, 2018 at 14:01
  • 1
    @KyawSiesein they problem with js variables is that you can't use them in ::before and ::after. Commented Aug 24, 2018 at 14:03
  • 2
    @KyawSiesein this is completely valid and normal in Design Systems. You certainly CAN define local CSS Variables inline. Commented Oct 13, 2022 at 10:28

11 Answers 11

378

Like this:

function Component() {
  const style = { "--my-css-var": 10 } as React.CSSProperties;
  return <div style={style}>...</div>
}

Or without the extra style variable:

function Component() {
  return <div style={{ "--my-css-var": 10 } as React.CSSProperties} />
}
Sign up to request clarification or add additional context in comments.

4 Comments

Me too right now, but sure, this dirty cast is the easiest solution for this case.
Initially I thought this solution wasn't good, but ultimately I probably agree. Extending React.CSSProperties to allow the variable will allow it everywhere, which is not bad, but is somewhat confusing and blows up the size of the code for something that's not that important. I guess I would just prefer if there was a way to add // @ts-expect-error only above that particular property, but the error is not there… unless perhaps there is a satisfies instead of as?
Why couldn't they put [key: --${string}]: string in the interface? Adding one line is hardly blowing up the size of the code.
This answer suppresses all type errors in the entire style definition. A better answer further down on this page uses ['--length' as any] to suppress just the variable's type error.
105

you can simply put this module declaration merge using string templates at the top of the file or in any .d.ts file, then you will be able to use any CSS variable as long it starts '--' and that is string or number

import 'react';

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

for example

<div style={{ "--value": percentage }} />

11 Comments

So you suggest to overwrite builtin type, what if you have 10 modules and each use different variables?
Remember to place import 'react' at the top!
@Daniel not necessary in react 17
I was able to use declare namespace React instead of declare module 'react'. Not sure what the differences are, though.
@ivanjonas declare namespace React works for d.ts files, on the other hand declare module 'react' works on .tsx
|
64

Casting the style to any defeats the whole purpose of using TypeScript, so I recommend extending React.CSSProperties with your custom set of properties:

import React, {CSSProperties} from 'react';

export interface MyCustomCSS extends CSSProperties {
  '--length': number;
}

By extending React.CSSProperties, you will keep TypeScript's property checking alive and you will be allowed to use your custom --length property.

Using MyCustomCSS would look like this:

const MyComponent: React.FC = (): JSX.Element => {
  return (
    <input
      style={
        {
          '--length': 300,
        } as MyCustomCSS
      }
    />
  );
};

6 Comments

This is interesting because now the style can have only specific custom properties. Not sure how another answers handle this, I also don't have any react/typescript project to test.
@jcubic because MyCustomCSS extends from CSSProperties other properties should be assignable as well.
I mean that I like the solution because I can only use custom properties I've defined in the type, so If I set --length: number; this will be the only custom property I can use. I know that rest of css works fine this is how extend works any language. by "can have only specific custom properties" I meant that for all custom properties in CSS only those that was defined by the type will be valid.
TS 4.4 supports template literal types, so something like this can be written instead of explicitly specifying all custom variables: [key: `--${string}`]: string | number;. One liner: type MyCustomCSS = CSSProperties & Record<`--${string}`, number | string>;
I'm marking yours as the accepted answer since this is the right way. Every other answer just ignores the types of custom property or modifies CSSProperties globally. Your solution actually makes the custom property part of the type locally per usage.
|
51

If you go to the definition of CSSProperties, you'll see:

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
     */
}

That page gives examples of how to solve the type error by augmenting the definition of Properties in csstype or casting the property name to any.

1 Comment

Link is out of date, if it was useful before it should likely be pointed at a commit rather than at master.
46

You can add a type assertion to the variable. i.e. {['--css-variable' as any]: value }

<table style={{['--length' as any]: array.length}}>
   <tbody>
      <tr>{array}</tr>
   </tbody>
</table>

1 Comment

suppressing typescript error is not the best solution
24

try:

<table style={{['--length' as string]: array.lenght}}>
  ...
</table>

2 Comments

It worked like a charm. I just didn't get why the string literal doesn't work since it's a string. Could you add more details to your answer? Thanks!
I was looking if anyone suggested this, this is the best approach in my opinion.
7
import "react";

type CustomProp = { [key in `--${string}`]: string };
declare module "react" {
  export interface CSSProperties extends CustomProp {}
}

put this in your global.d.ts file

6 Comments

How is it better than the existing answers ?
I don't think it's a good idea to overwrite react typescript types. So I would say it's a canonical example of a kludge.
@jcubic the React devs disagree with you, as evidenced by the comment quoted in stackoverflow.com/a/52013197/247482
@flyingsheep this is not overwriting the type, it only shows how the code looks like.
Why do you hint at the solution you came up with and call it “the actual” solution? We’re in real life, not an academic test. Therefore there’s only your solution, not the, and if you feel like it’s worth sharing, just do it. I didn’t sign up for a course, don’t treat me like a student.
|
1

I would like to add a different approach by using document.body.style.setProperty, and maybe if your css variable will be affected by certain props you can put it in a useEffect like this:

useEffect(() => {
    document.body.style.setProperty(
      "--image-width-portrait",
      `${windowSize.width - 20}px`
    );
}, [windowSize])

Later inside your css file you can call it like this:

width: var(--image-width-portrait);

1 Comment

This is a component that can have multiple instances on the page.
1

These are (well almost) all valid approaches to solve this, but there is another.

You could add the ref to your element and set the style where ever. I know that this would be quite possibly an improper use of useEffect but if you have something in useEffect that needs to happen on component mount then:

const tableRef = useRef<HTMLTableElement | null>(null)

useEffect(() => {
  tableRef?.current?.style.setProperty('--length': array.lenght);
}, [])
...

<table ref={tableRef}>
   <tbody>
      <tr>{array}</tr>
   </tbody>
</table>

This can also be used on any interaction and event

1 Comment

This will work but I don't think this is a good idea. If your CSS depends on the variable it may recalculate styles for no reason. It's better to render everything at once. Unless you use ref outside of React rendering.
1

Augment CSSProperties:

declare module "react" {
  interface CSSProperties {
    "--length"?: number;
  }
}

Then use CSSProperties as usual.

Some other answers recommends allowing all "--" keys. I would recommend specifying "--length" as it is useful in guarding against mistyped string literals.

This approach is also better than Benny Code's approach of defining interface MyCustomCSSProperties extends CSSProperties, because the "--length" property won't be able to interact nicely with any other interactions with types defined by dependencies that uses CSSProperties. Check out this post for a comparison between different approaches.

Another side point: This approach is recommended by the package author: https://github.com/frenic/csstype#what-should-i-do-when-i-get-type-errors The React CSSProperties derives from this linked package.

Comments

0

just to report something i found interesting:


style={{

  // somehow, this one line makes the red line dissappear
  ...background ? { background } : {}, 
      
  ...padding ? { '--toaster-margin': padding } : {},
  ...borderRadius ? { '--toaster-radius': borderRadius } : {},
  ...textColor ? { '--toaster-text-color': textColor } : {},
  ...closeColor ? { '--toaster-close-color': closeColor } : {},
  ...iconColor ? { '--toaster-icon-color': iconColor } : {},
  ...maxLine ? { '--toaster-max-line': maxLine } : {}
}}

and i'm able to compile

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.