Developing React component library in 2021

alvechy

Alexander Vechy

Posted on September 12, 2020

Developing React component library in 2021

In the previous article we've made our decisions on the tools we going to use for our new React component library. Let's recap:

  • we use stitches as our CSS-in-JS solution
  • dokz will power our documentation website
  • react-aria helps with accessibility
  • and Vercel will host all that

Assuming we already have a nice name and repository inside our organization, let's start from the documentation engine:

❯ yarn create dokz-app
yarn create v1.22.5
[1/4] πŸ”  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] πŸ”—  Linking dependencies...
[4/4] πŸ”¨  Building fresh packages...

success Installed "create-dokz-app@0.0.6" with binaries:
      - create-dokz-app
βœ” What is your project named? … awesome-ds
Enter fullscreen mode Exit fullscreen mode

This will quickly create folder structure and add required dependencies to package.json. dokz is built on top of Next.js, hence folder structure may seem familiar to you. If not, I encourage you to try Next.js on your own as it's so awesome that I don't want it to outshine this article's main purpose.

You can now install the dependencies and verify that it works:

yarn install
Enter fullscreen mode Exit fullscreen mode
yarn dev
Enter fullscreen mode Exit fullscreen mode

And we see it on http://localhost:3000/!

Initial setup preview

Awesome, let's move on. Now we need to add stitches and try it out. As I mentioned in the previous article, the setup is super simple. Stop the server first and execute:

yarn add @stitches/react
Enter fullscreen mode Exit fullscreen mode

And we're good to go! Let's create next two files: lib/stitches.config.js and lib/Box.js so that our fodler sturcutre is similar to:

awesome-ds
└───.next  
β”‚
└───lib
β”‚   β”‚   Box.js
β”‚   β”‚   stitches.config.js
β”‚   
└───node_modules
β”‚   
└───pages
|   ...
Enter fullscreen mode Exit fullscreen mode

Here I'm pushing two conventions: keep sources of the component library in lib folder (as it's shorter than components) and use PascalCase for React component names, as it's mentally easier to see that Box.js is a React component while box.js would be some utils file, not related to React.

stitches.config.js is where we create a styled function:

import { createStyled } from '@stitches/react';

export const { styled, css } = createStyled({});
Enter fullscreen mode Exit fullscreen mode

Here, we export styled and css returned by createStyled. This might seem inconvenient, as wouldn't it be easier to use:

import { styled } from '@stitches/react'
Enter fullscreen mode Exit fullscreen mode

But my take on this is that it's not only allows to have multiple instances of styled for whatever reason, but it also provides a nice way for TypeScript autocomplete. We will cover it later.

createStyled takes a configuration object we should use to make our design system to be a consistent system. If you're familiar with styled-system, then this library has it out of the box. It allows us to define reusable variables that will be replaced at runtime with defined values, common colors, consistent spacing, font sizes, etc. So, we start from tokens:

export const { styled, css } = createStyled({
  tokens: {
    colors: {
      $text: '#1A202C',
      $textLight: '#718096',
      $background: '#FFF',
      $accent: '#ED8936',
    },
    space: {
      $0: '0px',
      $1: '4x',
      $2: '8px',
      $3: '12px',
      $4: '16px',
      // etc
    },
    fontSizes: {
      $xs: '12px',
      $sm: '14px',
      $base: '16px',
      $lg: '18px',
      $xl: '20px',
    },
  }
});
Enter fullscreen mode Exit fullscreen mode

Here I used space scaling and colors from Tailwind CSS as I really love it. And now let's see how it works. Going to Box.js we create a simple component:

import { styled } from './stitches.config'

export const Box = styled('div', {
  // here we could define our custom styling if we needed to
})
Enter fullscreen mode Exit fullscreen mode

Oh wow, I'm really impressed. That's all we need to create the main building block of a component library! This simple component now accepts css property to be styled using our created config. Let's see how it works. Go to pages/index.mdx and let's leave only Playground for us to write some code:

---
name: Getting started
---

import { Playground } from 'dokz';
import { Box } from '../lib/Box';

## Box

<Playground>
  <Box
    css={{
      backgroundColor: '$text',
    }}
  >
    <Box as="p" css={{ color: '$accent' }}>
      Holy Moley!
    </Box>
  </Box>
</Playground>
Enter fullscreen mode Exit fullscreen mode

Saving, starting the docs server with yarn dev and see:

Styled Box component preview

That was easy. And you may ask "but how is it different from using style prop on just div? Well, as you can see, we use our defined tokens for colors and can expand it to font styles, spacing, border radiuses, z-indicies – everything that forms a design system!

We also know that we will use some properties very often, let's make them easier to use via stitches utils:

export const { styled, css } = createStyled({
  tokens: {
   // our defined tokens
  },
  utils: {
    p: () => value => ({
      paddingTop: value,
      paddingBottom: value,
      paddingLeft: value,
      paddingRight: value,
    }),
    px: () => value => ({
      paddingLeft: value,
      paddingRight: value,
    }),
    py: () => value => ({
      paddingTop: value,
      paddingBottom: value,
    }),

    bc: () => value => ({
        backgroundColor: value
    })
  }
});
Enter fullscreen mode Exit fullscreen mode

Now we can use it on our docs page to see how it works:

<Playground>
  <Box
    css={{
      bc: '$text', // instead of long backgroundColor
    }}
  >
    <Box as="p" css={{ color: '$accent', px: '$4' }}>
      Holy Moley!
    </Box>
  </Box>
</Playground>
Enter fullscreen mode Exit fullscreen mode

And that's it. We can use consistent spacing and colors in any of our components! utils is very powerful, use it for the properties you want to short-hand. For example, font can produce font-size, letter-spacing, line-height and font-family to fit for every need. Or maybe you want to do yAlign: '$center' to center your content vertically.

Note on usage

As you saw in our example, we inline objects passed as css prop. Opposite to many other CSS-in-JS solutions, this won't be extracted during build time. This means that css objects will be recreated on every component render. This won't be a problem for small applications, but I suggest to build useful habits even starting small, especially when it's that simple:

const styles = {
  wrapper: {
    bc: '$text',
  },
  text: { color: '$accent', px: '$4' },
};
const ConsumerComponent = () => (
  <Box css={styles.wrapper}>
    <Box as="p" css={styles.text}>
      Holy Moley!
    </Box>
  </Box>
);
Enter fullscreen mode Exit fullscreen mode

Another apprach would be to use styled function to compose class names. This will create a new component that will manage the class names passed as a className prop to the styled component itself:

const Wrapper = styled(Box, {
  bc: '$text',
})

const Text = styled(Box, {
  color: '$accent',
  px: '$4'
})

const ConsumerComponent = () => (
  <Wrapper>
    <Text as="p">
      Holy Moley!
    </Text>
  </Wrapper>
);
Enter fullscreen mode Exit fullscreen mode

Alright, this looks good to me, now we move can move on to the next component: Button. We are going to cover accessibility, TypeScript and see how we can bundle our components library

πŸ’– πŸ’ͺ πŸ™… 🚩
alvechy
Alexander Vechy

Posted on September 12, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related