167

I want to define jsx like this:

<table style={{'--length': array.lenght}}>
   <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?

jcubic
  • 61,973
  • 54
  • 229
  • 402
  • why do you want css variables ? Because you cannot use CSS variables in style tag. It accepts CSS properties only. You can create a variable like this `const length = array.length` and use that `length` in css ? – Kyaw Siesein Aug 24 '18 at 13:17
  • i believe this is discussed here https://github.com/facebook/react/issues/6411 – SGhaleb Aug 24 '18 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. – jcubic Aug 24 '18 at 14:01
  • 1
    @KyawSiesein they problem with js variables is that you can't use them in `::before` and `::after`. – jcubic Aug 24 '18 at 14:03
  • 1
    @KyawSiesein this is completely valid and normal in Design Systems. You certainly CAN define local CSS Variables inline. – androidavid Oct 13 '22 at 10:28

9 Answers9

271

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} />
}
Gio Polvara
  • 23,416
  • 10
  • 66
  • 62
ixrock
  • 2,870
  • 2
  • 13
  • 7
45

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.

isherwood
  • 58,414
  • 16
  • 114
  • 157
Matt McCutchen
  • 28,856
  • 2
  • 68
  • 75
  • 6
    Link is out of date, if it was useful before it should likely be pointed at a commit rather than at master. –  Apr 23 '21 at 03:14
42

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
      }
    />
  );
};
Benny Code
  • 51,456
  • 28
  • 233
  • 198
  • 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 Jan 29 '21 at 21:10
  • @jcubic because `MyCustomCSS` extends from `CSSProperties` other properties should be assignable as well. – Benny Code Mar 04 '21 at 10:09
  • 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. – jcubic Mar 04 '21 at 12:20
  • 10
    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>;``` – brc-dd Sep 25 '21 at 16:30
  • I prefer this one liner solution (@brc-dd) I have tested and I confirm it works perfectly! – Sparker73 Sep 25 '21 at 20:12
  • 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. – jcubic Feb 05 '23 at 21:57
41

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>
Aifos Si Prahs
  • 343
  • 1
  • 9
Carlos A. Cabrera
  • 1,920
  • 15
  • 14
38

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 }} />
Jayant Bhawal
  • 2,044
  • 2
  • 31
  • 32
mindlid
  • 1,679
  • 14
  • 17
  • 1
    So you suggest to overwrite builtin type, what if you have 10 modules and each use different variables? – jcubic Dec 17 '21 at 19:45
  • as you may know typescript interfaces are merged by default, they do not overwrite the existing, they extend them, source: https://www.typescriptlang.org/docs/handbook/declaration-merging.html – mindlid Dec 17 '21 at 22:59
  • I ended up using this solution, thanks. One thing to note: it's possible to pass in values with more dashes than two (as a dash is a string). For example, this will be fine: `style={{ ---color: 'red' }}`. This isn't an issue for my use case, but I just wanted to add this as a note if someone else wonders the same. – Jussi Virtanen Dec 23 '21 at 09:07
  • yes, you can use any valid javascript string as pattern in the typescript template literal string – mindlid Dec 28 '21 at 19:11
  • 2
    Remember to place `import 'react'` at the top! – Daniel Jan 31 '22 at 17:48
  • @Daniel not necessary in react 17 – mindlid Feb 01 '22 at 03:12
  • This is the one true answer. I wonder why this is not built in. Seems like generally accurate typing – Lukas Dec 17 '22 at 21:19
  • `TS2590: Expression produces a union type that is too complex to represent.` – Даниил Пронин Feb 21 '23 at 11:50
  • I was able to use `declare namespace React` instead of `declare module 'react'`. Not sure what the differences are, though. – ivanjonas Aug 05 '23 at 02:34
9

try:

<table style={{['--length' as string]: array.lenght}}>
  ...
</table>
HaNdTriX
  • 28,732
  • 11
  • 78
  • 85
  • 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! – Gustavo Straube Jun 15 '23 at 16:33
5
import "react";

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

put this in your global.d.ts file

BeanBro
  • 51
  • 1
  • 6
  • 1
    How is it better than the existing answers ? – XouDo Sep 27 '21 at 12:31
  • 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](https://terminal.jcubic.pl/404#[[0,1,%22jargon%20kludge%22]]). – jcubic Sep 27 '21 at 13:07
  • 1
    @jcubic the React devs disagree with you, as evidenced by the comment quoted in https://stackoverflow.com/a/52013197/247482 – flying sheep Dec 15 '21 at 10:06
  • 1
    @flyingsheep this is not overwriting the type, it only shows how the code looks like. – jcubic Dec 15 '21 at 11:18
  • @flyingsheep maybe I should give your the actual solution to the problem. It was not overwriting the builtin types. – jcubic Dec 15 '21 at 11:20
  • 1
    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. – flying sheep Dec 16 '21 at 13:24
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);
Kira
  • 171
  • 1
  • 4
  • 11
0

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

  • 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. – jcubic Feb 15 '23 at 11:57