Developing React component library in 2021
Alexander Vechy
Posted on September 12, 2020
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
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
yarn dev
And we see it on http://localhost:3000/
!
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
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
| ...
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({});
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'
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',
},
}
});
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
})
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>
Saving, starting the docs server with yarn dev
and see:
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
})
}
});
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>
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>
);
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>
);
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
Posted on September 12, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.