Search code examples
reactjstypescriptemotion

How to share styles, with props, between emotion/styled styled components, in Typescript


I'm using emotion styled components. I have a textinput and an input, and I want them to have identical styles. I'm doing this from within a .tsx file (that is to say, I'm writing typescript).

I believe it should look something along these lines:

someView.tsx

import styled from "@emotion/styled";
import { css } from "@emotion/react";


const InputStyling = css`
 width: 100%;
  border-style: solid;
  border-color: ${(props: { invalid?: boolean }) => props.invalid ? red : blue};
`;

export const BaseInput = styled.input<{invalid?: boolean}>`
  ${InputStyling}
`;

export const BaseTextarea = styled.textarea<{invalid?: boolean}>`
  ${InputStyling}
`;

This doesn't work, I get the following error when I typecheck:

➤ YN0000: src/design_system/TextInput/TextInput.tsx(19,22): error TS2769: No overload matches this call.
➤ YN0000:   Overload 1 of 2, '(template: TemplateStringsArray, ...args: CSSInterpolation[]): SerializedStyles', gave the following error.
➤ YN0000:     Argument of type '(props: {    invalid?: boolean;}) => string' is not assignable to parameter of type 'CSSInterpolation'.
➤ YN0000:       Type '(props: { invalid?: boolean | undefined; }) => string' is missing the following properties from type 'CSSInterpolation[]': pop, push, concat, join, and 25 more.
➤ YN0000:   Overload 2 of 2, '(...args: CSSInterpolation[]): SerializedStyles', gave the following error.
➤ YN0000:     Argument of type 'TemplateStringsArray' is not assignable to parameter of type 'CSSInterpolation'.
➤ YN0000:       Type 'TemplateStringsArray' is missing the following properties from type 'CSSInterpolation[]': pop, push, reverse, shift, and 6 more.

I've browsed many StackOverflow answers, trying slightly different incantations of above. For example, I've tried having an interface:

interface InputBoi {
  invalid?: boolean
}

export const BaseInput = styled.input<InputBoi>`
  ${resetInputCss}
  ${InputStyling}
`;

I got the same error.

I tried using the styled factory function style:

export const BaseInput = styled('input')<InputBoi>`
  ${resetInputCss}
  ${InputStyling}
`;

export const BaseTextarea = styled('textarea')<{invalid?: boolean}>`
  ${resetInputCss}
  ${InputStyling}
`;

Same error for both.

Emotion's composition style isn't exactly what I want, this app's structure prefers strongly to avoid any CSS within the render function.

Other StackOverflow answers deal with extending other components, but that's not what I want to do, I want to share css, not have one component extend another (because one needs to be input and the other needs to be textarea).

On the emotion repository, I've found people with similar issues, but no solution.

A CSSObject won't work for me, I need to pass props.

What is the correct way to share styles between two different components when using emotion/styled styled components in a typescript file?


Solution

  • The answer is to use a function that returns a styled css template string, like so:

    const getInputStyling = ({ invalid } : { invalid: boolean | undefined}) => css`
      width: 100%;
      border-color: ${invalid ? 'red' : 'black'};
    `;
    
    export const BaseInput = styled.input<{invalid: boolean | undefined}>`
      ${({invalid}) => getInputStyling({ invalid })}
    `;