DEV Community

Juan David C.
Juan David C.

Posted on

Start your app the right way! Featuring React, styled-system, styled components and Typescript

As an assignment for an app, I needed to implement a more consistent typography/theme. Consistency through your app is essential, and it also saves you much time. So instead of refactoring those buttons to set your font family, your margins, or your primary color, you can start with this and not worry about it anymore. There were numerous articles on how to approach this, and it's not something new, but writing about it seems like an excellent exercise to put what I've learned to practice. While reading these articles, I came across styled-system, an impressive library that makes it easy to make props available for your components. This library will be the foundation of this article. The articles will be listed at the end of the article.

I hope you enjoy this step by step tutorial on how to set up a style guide foundation for your app. We are going to do everything from scratch, starting with creating your app up until the style guide showcase in storybook. Let's get started!

If you don't fancy reading through the setup because you've done this a thousand times, here is how this article is written:

How this article is written:

Setup

Creating the app and installing extra packages

The create react app is a great start for a react app, we will use the Typescript flag to get all the nice features typescript gives us (especially those typescript 3.7 features)

npx create-react-app react-styleguide --typescript

Then we will install the other packages we need:

yarn add styled-components @types/styled-components styled-system @types/styled-system react-router-dom

Next we can add our dev dependencies:

yarn add -D prettier eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-config-prettier eslint-plugin-prettier

The last thing we have to install is storybook. Storybook will be used to create a reference showcase of our theme ( typography, colors, space, breakpoints).

npx -p @storybook/cli sb init --type react

Storybook is a convenient tool you can use to prototype your components. I wrote an article about Storybook Driven Development a while ago that also introduces the basics of storybook. You can read it here.

Prettier & Eslint config

We want our code to be nice and neat, so we are going to use prettier and eslint to keep us in our lanes. These were already installed in the steps before, but now we have to configure them.

Our eslint (.eslintrc) setup will look something like this:

{
      "parser": "@typescript-eslint/parser",
      "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:@typescript-eslint/recommended",
        "prettier/@typescript-eslint",
        "plugin:prettier/recommended"
      ],
      "parserOptions": {
        "ecmaVersion": 2018,
        "sourceType": "module",
        "ecmaFeatures": {
          "jsx": true
        }
      },
      "rules": {
        "prettier/prettier": [
          "error",
          {
            "singleQuote": true
          }
        ],
        "react/prop-types": "off",
        "@typescript-eslint/no-explicit-any": "error"
      },
      "settings": {
        "react": {
          "version": "detect"
        }
      }
    }
Enter fullscreen mode Exit fullscreen mode

And prettier (.prettierrc) like this:

{
  "trailingComma": "es5",
  "tabWidth": 2,
  "singleQuote": true,
  "printWidth": 140
}
Enter fullscreen mode Exit fullscreen mode

Both these files need to be created in the root of your project.

Storybook setup

Luckily for us, Storybook now works out of the box with Typescript. We just have to setup up a few things for future use. We will setup our ThemeProvider and the Router ( for Links ). You can copy-paste the code below in your config.js in the .storybook folder.

import { addDecorator, configure } from '@storybook/react';
import { ThemeProvider } from 'styled-components';
import React from 'react';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';

const history = createBrowserHistory();

addDecorator(story => (
  <ThemeProvider theme={{}}>
    <Router history={history}>{story()}</Router>
  </ThemeProvider>
));

configure(require.context('../src', true, /\.stories\.tsx$/), module);
Enter fullscreen mode Exit fullscreen mode

We do a few things in this file:

  • Setup the ThemeProvider with an empty object for now, because we don't have a theme yet
  • Setup our Router, so our Link component doesn't break in storybook
  • Change the context function, so it picks up our .tsx files.
  • Change the folder where our context function searches for stories ( I like to keep my stories together with the components)

Next, we can change the extension of the demo story that storybook provides (0-Welcome.stories.js ) from js to tsx and move it in our src folder to see if everything is working. Try running yarn storybook, and if you see the screen below, you have your storybook setup done. Good job! 🔥

Storybook running

If you don't see this, don't worry, I got you. You can just clone this branch I made as a checkpoint branch so you can continue to follow the tutorial

Alt text of image

Now we are ready to get our hands dirty, let's start with our theme structure!

Making our Theme structure

Our theme is the skeleton of the app, this is where we define our standards ( most of them ) and how these should be used. We are heavily relying on styled-system for this. There are a few things we want to define in our theme:

  • Space
  • Breakpoints ( for responsive fonts )
  • Colors

Let's start by making a folder in our src folder called styleguide. In this folder, we can create a defaulTheme.ts. In this file, we will define our theme. This theme will have the structure of the type provided by styled-system. You can read the specification of the theme here

We won't be using all these properties for this article, as it would just be too much work and probably a bit boring to read. So we are going to keep it simple by only setting up space, breakpoints, and colors.

Space

The point of defining your space is to avoid having inconsistent pixels all over the place in your app. With predefined space values, you are going to have more predictable results. You can set this up however you want, but I like the geometric progression explained in this article combined with the t-shirt sizes approach. It looks something like this:

export interface Space {
  NONE: number;
  XS: number;
  S: number;
  M: number;
  L: number;
  XL: number;
  XXL: number;
}

export const space: Space = {
  NONE: 0,
  XS: 2,
  S: 4,
  M: 8,
  L: 16,
  XL: 32,
  XXL: 64,
};

Enter fullscreen mode Exit fullscreen mode

Breakpoints

Next are our breakpoints. These breakpoints are mostly used with responsive fonts, I'll show it works later.

export const breakpoints: string[] = ['319px', '424px', '767px', '1023px'];
Enter fullscreen mode Exit fullscreen mode

Colors

These colors are highly opinionated, and you can set up your colors the way you want. I'll just give you an idea of how to set them up. Notice that I am using the csstype that styled-system uses.

import * as CSS from 'csstype';

export interface ThemeColors {
  primary: CSS.ColorProperty;
  link: CSS.ColorProperty;
  success: CSS.ColorProperty;
  warning: CSS.ColorProperty;
  error: CSS.ColorProperty;
  heading: CSS.ColorProperty;
  text: CSS.ColorProperty;
  disabled: CSS.ColorProperty;
  border: CSS.ColorProperty;
}

export const colors: ThemeColors = {
  primary: '#423EA2',
  link: '#1890ff',
  success: '#52c41a',
  warning: '#faad14',
  error: '#e84118',
  heading: '#423EA2',
  text: '#000',
  disabled: '#f5222d',
  border: '#423EA2',
};

Enter fullscreen mode Exit fullscreen mode

At last, our defaultTheme.ts:

import { Theme } from 'styled-system';
import { colors } from './colors';
import { space } from './space';

export const breakpoints: string[] = ['319px', '424px', '767px', '1023px'];

export const defaultTheme: Theme = {
  space: {
    ...space,
  },
  breakpoints,
  colors: {
    ...colors,
  },
};

Enter fullscreen mode Exit fullscreen mode

Once you define these you can use them in components like this:


const Button = styled.button`
  background-color: ${({ theme }) => theme.colors.primary};
  padding: ${({ theme }) => theme.space.M}px;
  margin: ${({ theme }) => theme.space.M}px;
  width: 200px;
  border: none;
  color: white;
  font-size: 14px;
  font-weight: 700;
  border-radius: 15px;
  letter-spacing: 2px;
  text-transform: uppercase;
`;
Enter fullscreen mode Exit fullscreen mode

And the result :

Button

Creating our dynamic styled component

Next up, we will be making our dynamic styled component with all the styled-system props built-in. Here is were styled-system shines as we only have to use the style functions they provide.

import React from 'react';
import styled from 'styled-components';
import {
  borderRadius,
  BorderRadiusProps,
  color,
  fontFamily,
  FontFamilyProps,
  fontSize,
  FontSizeProps,
  fontStyle,
  FontStyleProps,
  fontWeight,
  FontWeightProps,
  letterSpacing,
  LetterSpacingProps,
  lineHeight,
  LineHeightProps,
  size,
  SizeProps,
  space,
  SpaceProps,
  textAlign,
  TextAlignProps,
  textStyle,
  TextStyleProps,
} from 'styled-system';

export type StyledSystemProps =
  | SpaceProps
  | FontSizeProps
  | FontStyleProps
  | SizeProps
  | TextStyleProps
  | LetterSpacingProps
  | FontFamilyProps
  | FontWeightProps
  | BorderRadiusProps
  | FontFamilyProps
  | LineHeightProps
  | TextAlignProps
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  | { color: string; as?: keyof JSX.IntrinsicElements | React.ComponentType<any> };

export default styled.div`
  ${space}
  ${fontSize}
  ${fontStyle}
  ${size}
  ${color}
  ${textStyle}
  ${letterSpacing}
  ${fontFamily}
  ${fontWeight}
  ${borderRadius}
  ${lineHeight}
  ${textAlign}
`;
Enter fullscreen mode Exit fullscreen mode

This means that this component now has a bunch of props ready to be used for easy styling. This dynamic component will be the foundation for setting up all our typography types.

Setting up all the typography styles and components

Now we can start creating our text styles. I am not an expert on typography, but these guidelines from BBC will give you a good introduction to typography harmony. Our text styles will look like this:

import { colors } from './colors';
import { StyledSystemProps } from './DynamicStyledSystemComponent';

const fontFamilies: { heading: string; body: string } = {
  heading: 'Montserrat, serif',
  body: 'Raleway, sans-serif',
};

interface TypographyStyles {
  H1: StyledSystemProps;
  H2: StyledSystemProps;
  H3: StyledSystemProps;
  H4: StyledSystemProps;
  H5: StyledSystemProps;
  LargeLead: StyledSystemProps;
  SmallLead: StyledSystemProps;
  Paragraph: StyledSystemProps;
  SmallParagraph: StyledSystemProps;
  Link: StyledSystemProps;
}
export const typographyStyles: TypographyStyles = {
  H1: {
    fontSize: [50, 51, 52, 57],
    fontWeight: 700,
    fontFamily: fontFamilies.heading,
    as: 'h1',
  },
  H2: {
    fontSize: [37, 39, 41, 43],
    fontWeight: 700,
    color: colors.primary,
    fontFamily: fontFamilies.heading,
    as: 'h2',
  },
  H3: {
    fontSize: [27, 28, 30, 32],
    fontWeight: 700,
    fontFamily: fontFamilies.heading,
    as: 'h3',
  },
  H4: {
    fontSize: [18, 20, 22, 24],
    fontWeight: 700,
    color: colors.primary,
    fontFamily: fontFamilies.heading,
    as: 'h4',
  },
  H5: {
    fontWeight: 700,
    fontSize: [16, 17, 19, 21],
    fontFamily: fontFamilies.heading,
    as: 'h5',
  },
  LargeLead: {
    fontWeight: 500,
    fontSize: [18, 20, 22, 24],
    fontFamily: fontFamilies.heading,
    as: 'p',
  },
  SmallLead: {
    fontWeight: 500,
    fontSize: [17, 18, 19, 21],
    fontFamily: fontFamilies.heading,
    as: 'p',
  },
  Paragraph: {
    fontSize: [14, 15, 15, 16],
    fontWeight: 300,
    fontFamily: fontFamilies.body,
    as: 'p',
  },
  SmallParagraph: {
    fontSize: [13, 14, 14, 15],
    fontWeight: 300,
    fontFamily: fontFamilies.body,
    as: 'p',
  },
  Link: {
    fontWeight: 700,
    color: colors.primary,
    fontSize: [14, 15, 15, 16],
    fontFamily: fontFamilies.body,
  },
};
Enter fullscreen mode Exit fullscreen mode

With these text styles, we can create typography components. We can create a helper function createComponent that has two arguments: the corresponding styling props, and it's display name. The Link component is not created with the createComponent function because it needs to use the react-dom Link component.

import React from 'react';
import { Link as RouterLink, LinkProps } from 'react-router-dom';
import DynamicStyledSystemComponent, { StyledSystemProps } from './DynamicStyledSystemComponent';
import { typographyStyles } from './typographyStyles';

type AnchorProps = StyledSystemProps & Pick<LinkProps, 'to'> & { onClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void };

const Link: React.FC<AnchorProps> = ({ to, onClick, children, ...props }) => (
  <RouterLink to={to} onClick={onClick}>
    <DynamicStyledSystemComponent {...typographyStyles.Link} {...props}>
      {children}
    </DynamicStyledSystemComponent>
  </RouterLink>
);

interface TypographyComponentProps {
  H1: React.FC<StyledSystemProps>;
  H2: React.FC<StyledSystemProps>;
  H3: React.FC<StyledSystemProps>;
  H4: React.FC<StyledSystemProps>;
  H5: React.FC<StyledSystemProps>;
  LargeLead: React.FC<StyledSystemProps>;
  SmallLead: React.FC<StyledSystemProps>;
  Paragraph: React.FC<StyledSystemProps>;
  SmallParagraph: React.FC<StyledSystemProps>;
  Link: React.FC<AnchorProps>;
}

const createComponent: (textStyle: StyledSystemProps, displayName: string) => React.FC<StyledSystemProps> = (textStyle, displayName) => {
  const component: React.FC<StyledSystemProps> = props => (
    <DynamicStyledSystemComponent {...textStyle} {...props}>
      {props.children}
    </DynamicStyledSystemComponent>
  );
  component.displayName = displayName;
  return component;
};

export const Typography: TypographyComponentProps = {
  H1: createComponent(typographyStyles.H1, 'H1'),
  H2: createComponent(typographyStyles.H2, 'H2'),
  H3: createComponent(typographyStyles.H3, 'H3'),
  H4: createComponent(typographyStyles.H4, 'H4'),
  H5: createComponent(typographyStyles.H5, 'H5'),
  LargeLead: createComponent(typographyStyles.LargeLead, 'LargeLead'),
  SmallLead: createComponent(typographyStyles.SmallLead, 'SmallLead'),
  Paragraph: createComponent(typographyStyles.Paragraph, 'Paragraph'),
  SmallParagraph: createComponent(typographyStyles.SmallParagraph, 'SmallParagraph'),
  Link: Link,
};
Enter fullscreen mode Exit fullscreen mode

Now we can start using our Typography components. I will showcase this next in storybook.

Reference story in Storybook

We can showcase how our styleguide is setup by making a storybook story. This will serve as a reference if you ever want to know which Typography, color, space you want to use.

import React from 'react';
import { storiesOf } from '@storybook/react';
import { Typography } from './Typography';
import styled from 'styled-components';
import { colors } from './colors';
import { breakpoints } from './theme';
import { space } from './space';

const { H1, H2, H3, H4, H5, LargeLead, Link, Paragraph, SmallLead, SmallParagraph } = Typography;

storiesOf('Styleguide ', module)
  .addParameters({ viewport: { defaultViewport: 'default' } })
  .add('default', () => (
    <Container>
      <H2>Typography</H2>
      <Divider />
      <H1>H1: Animi aperiam, aspernatur culpa deserunt eaque, eius explicabo inventore ipsa laudantium</H1>
      <H2>H2: Consectetur consequuntur cum deserunt dignissimos esse fugiat inventore iusto, laboriosam maiores minima!.</H2>
      <H3>H3: Culpa dignissimos expedita facilis, fugiat minus odio reiciendis ut? Accusamus delectus dicta eius.</H3>
      <H4>H4: Accusamus ad adipisci alias aliquam aperiam autem, culpa dolorem enim error est eum.</H4>
      <H5>H5: Debitis distinctio dolorum fugiat impedit itaque necessitatibus, quo sunt? Atque consectetur, corporis.</H5>
      <LargeLead>LargeLead:Deleniti est facere id placeat provident sapiente totam vitae. Asperiores consequuntur eaque eum.</LargeLead>
      <SmallLead>SmallLead: At aut corporis culpa doloribus ea enim error est impedit, ipsum iure maxime molestiae omnis optio.</SmallLead>
      <Paragraph>
        Paragraph: Facilis hic iste perspiciatis qui quibusdam sint velit vero Animi doloremque esse ex iure perferendis.
      </Paragraph>
      <SmallParagraph>SmallParagraph: Ad animi at debitis eligendi explicabo facere illum inventore, ipsum minus obcaecati.</SmallParagraph>
      <Link to="/">Link: Lorem ipsum dolor sit amet, consectetur adipisicing elit.</Link>
      <Divider />
      <H2>Colors</H2>
      <Paragraph>These colors are defined in styleguide colors.ts.</Paragraph>
      <Divider />
      <GridContainer>
        <div>
          <SmallParagraph>Kind</SmallParagraph>
        </div>
        <div>
          <SmallParagraph>HEX</SmallParagraph>
        </div>
        <div>
          <SmallParagraph>Color</SmallParagraph>
        </div>
      </GridContainer>
      {Object.entries(colors).map(obj => (
        <GridContainer key={obj[0]}>
          <SmallParagraph>{obj[0]}</SmallParagraph>
          <SmallParagraph>{obj[1]}</SmallParagraph>
          <ColorCircle color={obj[1]} />
        </GridContainer>
      ))}
      <Divider />
      <H2>Breakpoints</H2>
      <Paragraph>These are the responsive breakpoints being used</Paragraph>
      <br />
      <FlexContainer>
        {breakpoints.map((key: string) => (
          <SmallParagraph key={key} m={4}>
            {key}
          </SmallParagraph>
        ))}
      </FlexContainer>
      <Divider />
      <H2>Space</H2>
      <FlexContainer>
        {Object.entries(space).map(obj => (
          <div key={obj[0]}>
            <SmallParagraph m={2}>
              <strong>{obj[1]}px</strong>
            </SmallParagraph>
            <SmallParagraph m={2}>{obj[0]}</SmallParagraph>
          </div>
        ))}
      </FlexContainer>
    </Container>
  ));

const Divider = styled.div`
  border: 1px solid #00000022;
  width: 100%;
  margin: ${({ theme }) => theme.space.M}px;
`;

const ColorCircle = styled.div<{ color: string }>`
  height: 20px;
  width: 20px;
  border-radius: 20px;
  background-color: ${({ color }) => color};
`;

const GridContainer = styled.div`
  display: grid;
  grid-template-columns: 150px 150px 150px;
  margin: ${({ theme }) => theme.space.M}px;
`;

const FlexContainer = styled.div`
  display: flex;
`;

const Container = styled.div`
  background-color: white;
  height: 100vh;
  padding: 16px;
`;
Enter fullscreen mode Exit fullscreen mode

Wrapping up

So this was my take on how to setup a styleguide for you next react app. Hope you enjoyed the post and until the next one! Cheers

Github repo

Sources

https://levelup.gitconnected.com/building-a-react-typography-system-f9d1c8e16d55
https://www.bbc.co.uk/gel/guidelines/typography
https://sproutsocial.com/seeds/visual/typography/
https://medium.com/eightshapes-llc/space-in-design-systems-188bcbae0d62

Top comments (11)

Collapse
 
pak11273 profile image
Isaac Pak

It's great that you have a variety of props you can use now to define your component but now your components are bloated. You are also using components in your styleguide that aren't even using styled-system (Divider, ColorCircle, GridContainer, etc). Why recreate the typography for standard html elements. Why not just use styled-component's GlobalStyles to standardized them. I think styled-components alone would be more performant without styled-system unless there's some tree shaking that can be utilized.

Collapse
 
blackr1234 profile image
blackr1234

npx create-react-app react-style guide --typescript

Should it be react-style-guide with two hyphens? As I find the command arguments not making sense to me. Thanks for the great article anyway.

Collapse
 
jdcas89 profile image
Juan David C.

I was actually aiming for npx create-react-app react-styleguide --typescript but you can choose any name you want. Thank you for finding my mistake! Glad you enjoyed the read.

Collapse
 
cadams profile image
Chad Adams

TypeScript gives React too much boilerplate.

Collapse
 
jdcas89 profile image
Juan David C.

Debatable. For the features you get in return I think it's a fair tradeoff.

Collapse
 
avatsaev profile image
Aslan Vatsaev

small price to pay for salvation

Collapse
 
whurleyedcc profile image
William Hurley

This would be much easier to follow along with if you specified which file each snippet should go.

Collapse
 
whurleyedcc profile image
William Hurley • Edited

One more thing I found. Your initial command to create the project, npx create-react-app react-styleguide --typescript isn't entirely correct and just creates a standard JS project. From create-react-app.dev/docs/adding-t... :

npx create-react-app my-app --template typescript
Collapse
 
jdcas89 profile image
Juan David C.

Thank you for your suggestions man. I will update the article when I get the chance this weekend.

Collapse
 
whurleyedcc profile image
William Hurley

It looks like there's also a missing dev dependency: @types/react-router-dom. You have it in your repo's package.json, but it's not listed above as a dependency to install. It is needed for LinkProps.

Collapse
 
psousa profile image
Pedro Sousa

Just wanted to take 2 minutes to say this was a cool article: very informative.