Styled Components & TypeScript - 😍

Posted on December 07, 2019

Styled Components happens to be one of my favorite CSS in JS libraries all time and have been part of almost all of my ReactJS projects.

As I’m transitioning most of my projects to include TypeScript, there are things I stumbled along, but there are things that feel perfect. Listing down some of them here.

1. Installing the types

Styled Components library does not ship with types. Instead we have to install it from the Definitely Typed repository.

npm i --save-dev @types/styled-components

2. Custom props

One of the major advantages of using a CSS-in-JS solution is the ability to pass custom props on runtime and adapt CSS accordingly.

const Heading = styled.h1<{ active: boolean }>`
color: ${props => (props.active ? 'red' : 'blue')};
`;

Just as in JSX Elements, you can pass the generic type with <> after the component. Now, your styled-component is typed and there would be a static error on the element if you have not passed active prop.

To use it for extending a component:

import Title from './Title';
const Heading = styled(Title)<{ active: boolean }>`
color: ${props => (props.active ? 'red' : 'blue')};
`;

However, do note that active as a prop is being passed to the Title component even though it is not explicity said so. If someone adds an optional active prop to the component later, this might be problematic. To avoid this, you can refractor to:

const Heading = styled(({ active, ...rest }) => <Title {...rest} />)<{
active: boolean;
}>`
color: ${props => (props.active ? 'red' : 'blue')};
`;

However, this syntax is obviously more convoluted and creates an extra component. Whether it is worth all the mess for uncovering an accidental prop is upto you.

3. Typing the theme

Styled Components has the ability to specify a theme with the help of ThemeProvider. You can later access the theme with ${props=>props.theme.main.something}. Even if we avoid everything else, just the autocomplete from the theme object is worth doing this for.

From the docs:

So the first step is creating a declarations file. Let’s name it styled.d.ts for example.

1// import original module declarations
2import 'styled-components';
3
4// and extend them!
5declare module 'styled-components' {
6 export interface DefaultTheme {
7 borderRadius: string;
8
9 colors: {
10 main: string;
11 secondary: string;
12 };
13 }
14}

But manually typing the theme like this is pain, mainly because you have to edit two different files everytime you add or remove something from the theme object. Instead you can do:

import {} from 'styled-components';
import theme from '../theme';
declare module 'styled-components' {
type Theme = typeof theme;
export interface DefaultTheme extends Theme {}
}

Here, we are making use of Typescript’s type inference for our theme object to do it for us 🙌.

4. Making use of css prop

There are two css functions in the Styled Components documentation for some reason. Here I’m talking about the css attribute that can be used on an element when the Babel plugin is enabled.

<div
css={`
display: flex;
`}
>
...
</div>

But TypeScript is not aware of this css property and produces an error. I don’t know about you, but those red lines do very well bother me 👻.

To get around this, you can add the following to the styled.d.ts:

1import {} from 'styled-components';
2import { CSSProp } from 'styled-components';
3
4declare module 'react' {
5 interface Attributes {
6 css?: CSSProp | CSSObject;
7 }
8}

5. Media templates

There is an easy for specifying media queries from the documentation, but while the syntax for it is user friendly, the implementation by itself is hard to reason about for TypeScript (and as it happens, for new users too).

Instead I find myself using a much simpler alternative:

1const customMediaQuery = (maxWidth: number) =>
2 `@media (max-width: ${maxWidth}px)`;
3
4const media = {
5 custom: customMediaQuery,
6 desktop: customMediaQuery(922),
7 tablet: customMediaQuery(768),
8 phone: customMediaQuery(576),
9};
10
11const Content = styled.div`
12 height: 3em;
13 width: 3em;
14 background: papayawhip;
15
16 /* Now we have our methods on media and can use them instead of raw queries */
17 ${media.desktop} {
18 background: dodgerblue;
19 }
20 ${media.tablet} {
21 background: mediumseagreen;
22 }
23 ${media.phone} {
24 background: palevioletred;
25 }
26`;
27
28render(<Content />);

Courtesy

That one pain point I still have is about the ref. Adding a ref to a styled component still gives me an error, the same as it did an year ago.

Otherwise, Styled Components 💙 TypeScript.

Buy me a coffeeBuy me a coffee