Introduction
You can find the deployed version of the design system here. 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.
 
npx create-react-app projectName --template typescript
- Next we will install the necessary libraries namely styled-components and styled-system.
 
npm install --save styled-components styled-system
- Next we will install type declarations.
 
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 -
- 
breakpointsan array for our breakpoints. This key is important it will be used by styled-system for handling responsive styling. - 
fontSizesan object containing all our fontSizes. This key will be used by the fontSize props provided by styled-system utility function typography. - 
spacean object containing all our spacing. This key will be used by the margin, padding, marginTop, paddingTop, etc. props provided by styled-system utility functionspace. - 
colorsan 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 -
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 -
import { ThemeProvider } from "styled-components";
Import the theme object and wrap our App component with the ThemeProvider.
<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
 
import styled from "styled-components";
- Then let us import 
layout, color, typography, spaceandcomposefrom styled-system 
import { compose, layout, color, typography, space } from "styled-system";
- Let us now create our component.
 
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
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 -
export function App() {
  return <Box bg="primary100">Hello there</Box>;
}
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.
import {
  compose,
  layout,
  color,
  typography,
  space,
  LayoutProps,
  ColorProps,
  TypographyProps,
  SpaceProps,
} from "styled-system";
Create a new type called BoxProps and use it like so -
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.
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 -
<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 -
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).
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 -
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.
import { variant, ResponsiveValue } from "styled-system";
type BoxOptions = {
  appearance?: ResponsiveValue<BoxVariants>;
};
Extend our BoxProps with the BoxOptions -
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 -
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 -
<Box appearance="secondary" color="white" fontSize="md">
  Hello there
</Box>
We can also pass Responsive Values to our variant like so -
<Box appearance={["secondary", "primary"]} color="white" fontSize="md">
  Hello there
</Box>
But we can go one step further we can directly use our
design tokensfrom our theme in the variants as values to our styles. Like instead ofbackground: theme.colors.success200we can dobackground: "success200"styled-system will pick the right token (success200) from the right theme key (colors).We can also use our system shorthands like
pinstead ofpaddingorbginstead of background orsizeinstead ofwidth & heightlike so : -
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 -
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 -
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 -
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 -
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.
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 -
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.
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 havedisplay = flexand we extend the type with theFlexboxPropstype from styled-system. - Second we create a new component called 
BaseFlex, by composing our box component and passing it theflexboxutility function. - By composing our 
Box, we extend it meaning ourBaseFlexalso 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-systemswe 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 
Flexand in future can also extend theFlexto compose new components that will inherit all props & styling of theFlexcomponent. 
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 -
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 -
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.
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.



    
Top comments (1)
Loving this series! It would be extra awesome if you could cover how to create input/form controls to go with this!