Working with React, Styled Components and Styled System using TypeScript
Tags: react, typescript
Published: 2021-08-07 09:18:43

### Introduction

You can find the deployed version of the design system [here](http://yaldram-chakra-ui-clone.s3-website.ap-south-1.amazonaws.com/). In this post we are going to use `styled-system` with `styled-components` using TypeScript. This is not an introductory post the reader should be familiar with React and styled-components. If you want to learn more about styled-system check these posts -

- https://www.useanvil.com/blog/engineering/understanding-the-styled-system/.
- https://daily.dev/blog/building-react-micro-components-with-styled-system

### Prerequisite

Lets start from the scratch : -

- We will bootstrap a new react project with typescript.

```bash
npx create-react-app projectName --template typescript
```

- Next we will install the necessary libraries namely styled-components and styled-system.

```bash
npm install --save styled-components styled-system
```

- Next we will install type declarations.

```bash
npm install --save-dev @types/styled-components @types/styled-system
```

### Theme Object

I would like you to check out the styled-system api page (https://styled-system.com/api), it lists all the utility props provided by the library along with the **theme key** that a particular prop uses. We will only use some of the keys for simplicity. So our first task is to setup a theme object and it will consist of the following keys -

- `breakpoints` an array for our breakpoints. This key is important it will be used by styled-system for handling responsive styling.
- `fontSizes` an object containing all our fontSizes. This key will be used by the fontSize props provided by styled-system utility function typography.
- `space` an object containing all our spacing. This key will be used by the margin, padding, marginTop, paddingTop, etc. props provided by styled-system utility function `space`.
- `colors` an object containing all our colors. This key will be used by the color, background props provided by styled-system utility function color.

### Theme Setup

Under our src folder create a new folder called theme and create an `index.ts`. Lets create some basic values for the above theme keys under the index file -

```tsx
export const defaultTheme = {
  breakpoints: ["450px", "600px", "960px", "1280px", "1920px"],

  fontSizes: {
    xs: "0.75rem",
    sm: "0.875rem",
    md: "1rem",
    lg: "1.125rem",
    xl: "1.25rem",
  },

  space: {
    xxs: "0.6rem",
    xs: "0.8rem",
    sm: "1rem",
    md: "1.2rem",
    lg: "1.5rem",
    xl: "2rem",
  },

  colors: {
    white: "#fff",
    black: "#000",

    primary100: "#C6CAFF",
    primary200: "#5650EC",
    primary500: "#3B35DC",

    success100: "#E6FAE7",
    success200: "#52B45A",
    success500: "#2F9237",

    danger100: "#FFECEC",
    danger200: "#E02F32",
    danger500: "#BB1316",

    warning100: "#FFF5EF",
    warning200: "#F17D39",
    warning500: "#D35E1A",
  },
};

export type AppTheme = typeof defaultTheme;
```

The above code is self explanatory we created the theme object with the 4 specified theme keys discussed in the previous section. We are exporting the theme object and its type declaration from this file.

Now under index.tsx -

```tsx
import { ThemeProvider } from "styled-components";
```

Import the theme object and wrap our App component with the ThemeProvider.

```tsx
<ThemeProvider theme={defaultTheme}>
  <App />
</ThemeProvider>
```

### Box Component

Under the src folder create components folder and create a Box folder under the components folder. Create an index.tsx file under the Box folder. In this file we will create our first component using styled-component and styled-system utility functions.

- Imports

```tsx
import styled from "styled-components";
```

- Then let us import `layout, color, typography, space` and `compose` from styled-system

```tsx
import { compose, layout, color, typography, space } from "styled-system";
```

- Let us now create our component.

```tsx
export const Box = styled.div`
  box-sizing: border-box;
  ${compose(layout, color, typography, space)}
`;
```

There you go its done, we use compose if we are to use multiple utility functions for our component, else we would do

```tsx
export const Box = styled.div`
  box-sizing: border-box;
  ${color}
`;
```

### Typing the Box Component

Lets import the component in App.tsx and use it as follows -

```tsx
export function App() {
  return <Box bg="primary100">Hello there</Box>;
}
```

![type-error](https://res.cloudinary.com/dkl3acuus/image/upload/v1628325093/posts/react-styled-system-ts/one_kgf3vf.png)

But we have type errors well that is because TypeScript does not know this component takes in a bg prop, so let us type the Box Component. We have 4 styled system utilities `layout, color, typography and space` lets us import the same named props from styled-system.

```tsx
import {
  compose,
  layout,
  color,
  typography,
  space,
  LayoutProps,
  ColorProps,
  TypographyProps,
  SpaceProps,
} from "styled-system";
```

Create a new type called BoxProps and use it like so -

```tsx
type BoxProps = LayoutProps & ColorProps & TypographyProps & SpaceProps;

export const Box = styled.div<BoxProps>`
  box-sizing: border-box;
  ${compose(layout, color, typography, space)}
`;
```

Head over to App.tsx now the errors are gone. Run the app see our component. It should have a backgroundColor with the value of #C6CAFF, meaning styled prop picked this from our theme which is great. Similarly we can pass other props, lets add some padding, color, fontSize.

![autocompletion](https://res.cloudinary.com/dkl3acuus/image/upload/v1628325252/posts/react-styled-system-ts/two_tne38x.png)

Hit CTRL+SPACE and you get auto-completion for your props. Check the list of props available we also hand shorthands like we use `bg` instead of `background` we can also use `p` instead of `padding`. Here is the complete code in App.tsx -

```tsx
<Box bg="primary100" padding="md" color="white" fontSize="lg">
  Hello there
</Box>
```

### Typing the Theme Values

One thing you might have noticed we don't get auto-completion for the values of styled props from our theme keys. No problem we can type our styled props, import AppTheme in the BoxComponent and make the following changes to the BoxProps -

```tsx
type BoxProps = LayoutProps &
  ColorProps<AppTheme> &
  TypographyProps<AppTheme> &
  SpaceProps<AppTheme>;
```

The Styled System exposes these Generic types to which we pass our theme type. The ColorProps will pick the type of the color key of our theme object, the space props will pick the space key and so on. For the layout we don't pass our AppTheme type because we don't have a corresponding `size` key setup in our theme, you can add it if you want more on that (https://styled-system.com/api/#layout).

![tokens-autocompletion](https://res.cloudinary.com/dkl3acuus/image/upload/v1628325633/posts/react-styled-system-ts/three_nudj0p.png)

Now hit CTRL+SPACE and we get auto-completion for our `design tokens`. But there are some caveats, like you now cannot pass any other value other than your theme values. So if you want to pass any other value first add it to your theme.

### Variants

Use the variant API to apply complex styles to a component based on a single prop. This can be a handy way to support slight stylistic variations in button or typography components (from the docs).

For the sake of simplicity we will built 2 variants for our box namely `primary` & `secondary`. For each of these we will change the bg, color, padding, width and height of the Box.

Lets first add the type -

```tsx
type BoxVariants = "primary" | "secondary";
```

We will create another type called `BoxOptions` that will have additional options that we can pass to our Box as props.

```tsx
import { variant, ResponsiveValue } from "styled-system";

type BoxOptions = {
  appearance?: ResponsiveValue<BoxVariants>;
};
```

Extend our BoxProps with the BoxOptions -

```tsx
type BoxProps = LayoutProps &
  ColorProps<AppTheme> &
  TypographyProps<AppTheme> &
  SpaceProps<AppTheme> &
  BoxOptions;
```

So our variant prop will be called appearance and it will be of type BoxVariants, notice the ResponsiveValue generic type this enables us to pass arrays for responsive values (https://styled-system.com/responsive-styles). Now lets build our variants -

```tsx
export const Box = styled.div<BoxProps>`
  box-sizing: border-box;

  ${({ theme }: { theme: AppTheme }) =>
    variant({
      prop: "appearance",
      variants: {
        primary: {
          background: theme.colors.primary100,
          padding: theme.space.md,
          width: "200px",
          height: "200px",
        },
        secondary: {
          background: theme.colors.success200,
          padding: theme.space.lg,
          width: "300px",
          height: "300px",
        },
      },
    })}

  ${compose(layout, color, typography, space)}
`;

Box.defaultProps = {
  appearance: "primary",
};
```

To the variant function we pass the prop that our component will take and our variants object, each key of which will be a variant. **Also note that compose is called after variant**, this is done so that our styled props can override the variant styles. Say we had this rare case where we want primary variant styles to apply on our Box but we want to override the padding to say 'sm' in that case we can do so. Had our variant function was called before compose we would not be able to override its style . Now under App.tsx play around with the variants -

```tsx
<Box appearance="secondary" color="white" fontSize="md">
  Hello there
</Box>
```

We can also pass Responsive Values to our variant like so -

```tsx
<Box appearance={["secondary", "primary"]} color="white" fontSize="md">
  Hello there
</Box>
```
- But we can go one step further we can directly use our `design tokens` from our theme in the variants as values to our styles. Like instead of `background: theme.colors.success200` we can do `background: "success200"` styled-system will pick the right token (success200) from the right theme key (colors). 

- We can also use our system shorthands like `p` instead of `padding` or `bg` instead of background or `size` instead of `width & height` like so : - 

```tsx
export const Box = styled.div<BoxProps>`
  box-sizing: border-box;

  ${variant({
      prop: "appearance",
      variants: {
        primary: {
          bg: "primary100",
          p: "md",
          size: "200px"
        },
        secondary: {
          bg: "success200",
          p: "lg",
          size: "300px"
        },
      },
    })}

  ${compose(layout, color, typography, space)}
`;

Box.defaultProps = {
  appearance: "primary",
};
```
- Difference between the two is that for the latter you don't get type-safety, but given the fact many designers hand over designs with `design tokens`, it is not a big issue. I would say use whatever you find fit. I will use the first approach for the sake of this tutorial.

### System

This one is really interesting. To extend Styled System for other CSS properties that aren't included in the library, use the system utility to create your own style functions. (https://styled-system.com/custom-props).

Let us extend our BoxComponent's styled system props to also accept `margin-inline-start` and `margin-inline-end` properties and we will shorten the name to `marginStart` and `marginEnd` as follows -

```tsx
export const Box = styled.div<BoxProps>`
  box-sizing: border-box;

  ${({ theme }: { theme: AppTheme }) =>
    variant({
      prop: "appearance",
      variants: {
        primary: {
          background: theme.colors.primary100,
          padding: theme.space.md,
          width: "200px",
          height: "200px",
        },
        secondary: {
          background: theme.colors.success200,
          padding: theme.space.lg,
          width: "300px",
          height: "300px",
        },
      },
    })}

  ${system({
    marginStart: {
      property: "marginInlineStart",
      scale: "space",
    },
    marginEnd: {
      property: "marginInlineEnd",
      scale: "space",
    },
  })}

  ${compose(layout, color, typography, space)}
`;
```

We call the system function and pass it an object, whose keys are our property names we intend to extend the system with and the value of these keys is another object to which we pass in

- property the actual CSS Property we intend to add,
- and the scale which is the theme key that the system should look into to find the respective token.

So to scale we passed space if we use marginStart prop like marginStart="md" styled system will look into the space key of theme object to find the md value. There are also other options you can read about them in the docs.

We can also target multiple properties using system, say we want maxSize prop which takes in a value and sets the maxWidth and maxHeight for our box we can -

```tsx
system({
  maxSize: {
    properties: ["maxHeight", "maxWidth"],
  },
});
```

We can add additional CSS properties as props using a shorthand a good example might be the `flex` property like so -

```tsx
system({
  flex: true,
});
```

This is pretty handy as we don't want to pick any values from the theme and also if the name matches our CSS property we can just pass `true` as the value for the prop key.

**System is a core function of the library many other utility functions like the color we imported are nothing but made from system()** - https://github.com/styled-system/styled-system/blob/master/packages/color/src/index.js

Now lets add the `marginStart` and `marginEnd` to our `BoxProps` type, we will do the following -

```tsx
type BoxOptions = {
  appearance?: ResponsiveValue<BoxVariants>;
  marginStart?: SpaceProps<AppTheme>["marginLeft"];
  marginEnd?: SpaceProps<AppTheme>["marginLeft"];
};
```

I used the marginLeft field from the SpaceProps because it has the same type and just passed the AppTheme to the SpaceProps so that my values for the prop are typed. `Also you can pass responsive values to these props`.

![autocompletion-system](https://res.cloudinary.com/dkl3acuus/image/upload/v1628326097/posts/react-styled-system-ts/four_zt8mxt.png)

Hit CTRL+SPACE to see the autoCompletion for our custom system prop.

### Composing Components

One Common pattern working with `styled-components` is composition. Let us now create a new component called `Flex` which will extend our `Box` component. To `Flex` we will pass the `flexbox` utility function which then enables us to pass props like `justifyContent, alignItems` other flexbox props.

First create a new folder under components called Flex and create an index.tsx file. Lets import some stuff -

```tsx
import * as React from "react";
import styled from "styled-components";
import { flexbox, FlexboxProps } from "styled-system";

import { Box, BoxProps } from "../Box";
```

Make sure that you have exported `BoxProps`. Let us now compose the Box component and create a new component called `BaseFlex` and pass the `flexbox` utility function to it. Also create a type for it.

```tsx
type FlexProps = Omit<BoxProps, "display"> & FlexboxProps;

const BaseFlex = styled(Box)<FlexProps>`
  display: flex;
  ${flexbox}
`;
```

- We first create a type for our props, we will extend `BoxProps`, we have omitted display prop as our component will always have `display = flex` and we extend the type with the `FlexboxProps` type from styled-system.
- Second we create a new component called `BaseFlex`, by composing our box component and passing it the `flexbox` utility function.
- By composing our `Box`, we extend it meaning our `BaseFlex` also takes in all props that we pass to Box, inherits the variants and the system extensions we had for our box (marginStart & marginEnd).
- In many `design-systems` we have a base Box component and other components tend to extend it, mainly the Layout components like Flex, Stack, Grid, etc.
- We can also add variants and extend the system styles for just our `Flex` and in future can also extend the `Flex` to compose new components that will inherit all props & styling of the `Flex` component.

```tsx
export const Flex = React.forwardRef<HTMLDivElement, FlexProps>(
  (props, ref) => <BaseFlex ref={ref} {...props} />
);
```

Last step we create our actual Flex Component, using React.forwardRef. I actually follow this pattern if you are to pass a ref to a component that is composed and also if you were to have additional custom props or manipulate incoming props before passing them to the styled() function.

There are some TypeScript errors in the Flex component. Some minor type changes are needed to the BoxProps -

```tsx
export type BoxProps = BoxOptions &
  LayoutProps &
  ColorProps<AppTheme> &
  TypographyProps<AppTheme> &
  SpaceProps<AppTheme> &
  React.ComponentPropsWithoutRef<"div"> & {
    as?: React.ElementType;
  };
```

We extend the BoxProps with all additional props a div might accept using `React.ComponentPropsWithoutRef<"div">` this will solve the TypeErrors in the Flex. Also we will type the polymorphic as prop that we get when we use styled().

Check the Flex Component in App.tsx -

```tsx
export function App() {
  return (
    <Flex justifyContent="space-between" color="white" size="auto">
      <Box bg="danger500" size="140px">
        Hello One
      </Box>
      <Box bg="success500" size="140px">
        Hello Two
      </Box>
    </Flex>
  );
}
```

Play with the props and autocompletion, also notice I passed `size=auto` this is because our Flex inherits Box and our Box has a default variant of primary with only width and height of 200 so we just overwrite its dimensions. `size` prop comes with the `layout` utility function of styled-system it is used to set height and width.

<hr>

And there you go this was a very basic introduction to styled-system using it with TypeScript. This is my first post, your valuable feedback and comments will be highly appreciated.

Also I have used `styled-components, styled-system` with TypeScript to create a small clone of the awesome Chakra UI Elements I am planning to write a series of posts explaining how I did it. In the meantime do check out my github repo here - https://github.com/yaldram/chakra-ui-clone.

Thanks a lot for reading, until next time, PEACE.