
Posted on • Edited on • Originally published atclaritydev.net
Building component library with Docz and Lerna
The article was originally posted onmy personal blog.
Component libraries are all the rage these days, with many companies rolling out their own solutions or sticking to a bunch of open source alternatives. Leveraging a component library for UI development, particularly in large teams, has a lot of cool benefits. It allows to take full advantage of modular and reusable UI components, which brings increased speed of development and unifies styles across multiple teams and apps. Combine that with a robust design system, and the handover from design to development teams becomes smooth and more efficient.
Frameworks/libraries like React, Vue, etc are perfectly suited for this purpose since there are designed to be highly modular. In this post React and Styled components are used as main tools of choice for developing components.
There are also some helpful tools, that could be leveraged to speed up the development process and deployment of the library. Embracing the modular approach, it would make sense that each component would be an own npm package, the whole library being a monorepo. That's whereLerna will be used to manage multiple packages inside the project, as well as to keep track of their versioning and publishing process.
In order to test and document the components, Docz is used (as an alternative toStorybook). It allows documenting components withMDX, which is a format that combines JSX and Markdown, basically making it possible to import React components inside Markdown files. Moreover, Docz version 2 runs onGatsbyJS, which brings increased development and build speeds and enables access to Gatsby's vast network of plugins and tools.
Lerna setup
We'll start by creating a new project, titleduikit, and installing the required dependencies.
$npm i-g lerna$mkdiruikit&&cd$_$yarn add docz react react-dom styled-components
With the core dependencies installed, it's time to initialize the Lerna project.
$lerna init
This will create the following project structure:
ui-kit/ packages/ package.json lerna.json
The UI components will be stored in thepackages
folder.
Now let's examine the generatedlerna.json
, which serves as a configuration file for Lerna. By default there isn't much going on, and after a few customizations the config will look as follows.
{"npmClient":"yarn","version":"independent","packages":["packages/*"],"useWorkspaces":true}
The most important changes here are selectingyarn
as npm client, specifyingindependent
versioning, so the package versions can be changed independently of each other, and enablingYarn workspaces. Thepackages
option points to the location of our library packages, for which we'll keep the default setting. The more extensive list of configuration options is available on Lerna'sGithub page.
Additionally, we'll need to add workspaces-related options to the rootpackage.json.
{"name":"uikit","license":"MIT","workspaces":{"packages":["packages/*"]},"private":true,"dependencies":{"docz":"^2.2.0","lerna":"^3.20.2","react":"^16.12.0","react-dom":"^16.12.0","styled-components":"^5.0.0"},"devDependencies":{"prettier":"^1.19.1"}}
Here we specify the path toworkspaces
, which is the same as the one inlerna.json
. Also we have to make the package private, otherwise workspaces won't work.
Creating the first component
To kick things off with the dev work, let's add the first package -Typography
, with the necessary base font components. As a result, the project's structure will be updated as follows.
ui-kit/ packages/ typography/ src/ index.js CHANGELOG.md package.json package.json lerna.json
Before actually writing the font components, let's make a few modifications to the typography's package.json
.
{"name":"@uikit/typography","version":"1.0.0","description":"Base fonts","main":"dist/index.js","module":"src/index.js","files":["dist","CHANGELOG.md"],"author":"","license":"MIT"}
The most interesting here aremain
,module
andfiles
fields. We'll pointmain
to thedist
folder, where the transpiled files will be stored and later used in the installed package. Themodule
will point to thesrc
folder, so the packages can be imported directly from the source folder during development and the changes will be reflected immediately without needing to bootstrap packages again or run build script. Finally thefiles property contains the list of the files, which will be included in the published package.
Now we can setup some basic font styles intypography
'sindex.js
. Those will be made as styled components.
// typography/src/index.jsimportstyled,{css}from"styled-components";constfontFamily="sans-serif";constfontWeights={light:300,regular:400,bold:600};constbaseStyles=css` font-family${fontFamily}; margin: 0; padding: 0; -webkit-font-smoothing: antialiased; font-weight:${({fontWeight})=>fontWeights[fontWeight]||fontWeights.regular}; `;exportconstH1=styled.h1`${baseStyles}; font-size: 62px; letter-spacing: -3px; line-height: 62px; `;exportconstH2=styled.h2`${baseStyles}; font-size: 46px; letter-spacing: -3px; line-height: 46px; `;exportconstH3=styled.h3`${baseStyles}; font-size: 30px; letter-spacing: -2px; line-height: 30px; `;exportconstH4=styled.h4`${baseStyles}; font-size: 24px; letter-spacing: -1.5px; line-height: 24px; `;exportconstH5=styled.h5`${baseStyles}; font-size: 20px; letter-spacing: -1px; line-height: 20px; `;exportconstH6=styled.h6`${baseStyles}; font-size: 18px; letter-spacing: 0; line-height: 18px; `;exportconstText=styled.p`${baseStyles}; font-size: 16px; letter-spacing: 0; line-height: 16px; `;exportconstSmallText=styled.small`${baseStyles}; font-size: 12px; letter-spacing: 0; line-height: 12px; `;
Note thatcss
helper fromstyled-components
is used to define reusable parts of the styles, which are then extended by other components. The components also accept afontWeight
property for customization, which defaults toregular
.
Trying out Docz's playground
This seems like a good time to try these components out in action and that's whereDocz
will be used to document their usage. In order to do that, we'll need to add an.mdx
file somewhere in the project with the component documentation, and one of those files needs to point toroute: /
and will be used as the front page. Let's create thisindex.mdx
in the root of thepackages
.
// index.mdx --- name: Welcome route: / --- # Welcome to the awesome UI Kit Select any of the components from the sidenav to get started.
After runningyarn docz dev
, we can navigate tolocalhost:3000
and see the front page of the library.
To add documentation to the typography, we'll create adocs
folder inside the package and addtypography.mdx
there.
ui-kit/ packages/ typography/ docs/ typography.mdx src/ index.js CHANGELOG.md package.json package.json lerna.json
To document components, we'll use a special docz component, called Playground
. Wrapping it around the components will allow editing them right below where they are displayed.
--- name: Typography menu: Components --- import { Playground } from 'docz'; import { H1, H2, H3, H4, H5, H6, Text, SmallText } from '../src/index'; # Base Typography <Playground> <H1>Heading 1</H1> <H2>Heading 2</H2> <H3>Heading 3</H3> <H4>Heading 4</H4> <H4 fontWeight='bold'>Heading 4 bold</H4> <H5>Heading 5</H5> <H6>Heading 6</H6> <Text>Text</Text> <SmallText>SmallText</SmallText> </Playground>
After refreshing the page, or restarting dev sever if necessary, we'd be able to see our typography components. And the best thing is that we can directly edit the code on the page and see the updated results immediately!
Adding custom fonts
This works well for built-in fonts, but what if we want to load a custom font, say from Google fonts? Unfortunately, since v2 of Docz has been released quite recently and due to it being a major rewrite of v1, there's still no clear, documented way to do that. However, there's onesolution, which also nicely demonstrates the extendability of Gatsby configuration and a concept, known asComponent shadowing.
For Gatsby-specific components we'll need to create asrc
folder in the root of the project, where the theme-specific components, among others, will be stored. Since we're extending gatsby-theme-docz
, a folder with this name needs to be created inside thesrc
. Lastly, we'll create awrapper.js
file inside of it to have the following project structure.
ui-kit/ packages/ typography/ docs/ typography.mdx src/ index.js CHANGELOG.md package.json src/ gatsby-theme-docz/ wrapper.js package.json lerna.json
Insidewrapper.js
we'll add a very simple component, the only task of which is to pass down its children.
// src/gatsby-theme-docz/wrapper.jsimportReact,{Fragment}from"react";exportdefault({children})=><Fragment>{children}</Fragment>;
It seems quite pointless to make a component which only forwards the children, however the reason for this is that we can now includecss
styles in this component, which will be applied globally. For that, let's createstyles.css
alongsidewrapper.js
and import there one of the selected fonts. In this tutorial, we'll be using Montserrat.
/* src/gatsby-theme-docz/styles.css */@importurl('https://fonts.googleapis.com/css?family=Montserrat:300,400,600&display=swap');
Now we just need to import this file intowrapper.js
and update thefontFamily
constant for the typography.
// src/gatsby-theme-docz/wrapper.jsimportReact,{Fragment}from"react";import"./style.css";exportdefault({children})=><Fragment>{children}</Fragment>;
// ./packages/typography/src/index.jsimportstyled,{css}from"styled-components";constfontFamily="'Montserrat', sans-serif";// ...
The changes should be visible immediately (if not, might need to restart the dev server). This might not be the cleanest approach, but it gets the job done, and since it's no longer possible to load custom fonts viadoczrc.js
, this might be one of the few viable solutions.
Customizing the documentation site
Talking aboutdoczrc.js
, which is used to configure a Docz project. The list of configuration options can be found on the project'sdocumentation site. Since we're now using Montserrat font for UI kit's typography, it would make sense if our documentation website used the same font. To do that, we'll add a themeConfig
property to the doczrc.js
, where the styles for the most commonly used text elements will be applied.
constfontFamily="'Montserrat', sans-serif";exportdefault{title:"UI Kit",description:"UI Kit - Collection of UI components",themeConfig:{styles:{h1:{fontFamily:fontFamily},h2:{fontFamily:fontFamily},body:{fontFamily:fontFamily}}}};
Since we need to keep our project configuration separate from the components, we'll have to declare the font family separately here and use it for specific text elements. Additionally, we can customize the project title and description here. The defaultthemeConfig
can be found on theDocz's Github page. More options to customize the project, like adding a custom logo, are described in thedocumentation.
Adding Buttons
Finally it's time to add a React component, Buttons,
which will also make use of the typography for better illustration of how components can be used together. As before, we'll make a new package, so the project's structure will be as follows.
ui-kit/ packages/ typography/ docs/ typography.mdx src/ index.js CHANGELOG.md package.json buttons/ docs/ buttons.mdx src/ index.js Buttons.js CHANGELOG.md package.json src/ gatsby-theme-docz/ style.css wrapper.js package.json lerna.json
Thepackage.json
forbuttons
will look almost identical to the one fromtypography
, with a few small exceptions. The most notable one is thatbuttons
hastypography
package as a dependency.
{"name":"@uikit/buttons","version":"1.0.0","description":"Button components","main":"dist/index.js","module":"src/index.js","files":["dist","CHANGELOG.md"],"dependencies":{"@uikit/typography":"^1.0.0"},"author":"","license":"MIT"}
Now, after we runlerna bootstrap
, it will install all the required packages and symlink the dependencies inside the packages
folder. One nice benefit of this is that if we make any changes to thetypography
package and use that package insidebuttons
, the changes will be immediately reflected in both packages without needing to rebuild or publish any of them. This makes the development experience really fast and efficient!
After all the dependencies have been installed, we can start writing code for the buttons.
// packages/buttons/src/Buttons.jsimportReactfrom"react";importstyledfrom"styled-components";import{SmallText}from"@uikit/typography";exportconstButtonSmall=({text,...props})=>{return(<Button{...props}><SmallText>{text}</SmallText></Button>);};exportconstButton=styled.button` border-radius: 4px; padding: 8px 16px; color: white; background-color: dodgerblue; border-color: dodgerblue; `;// packages/src/buttons/index.jsexport*from"./Buttons";
Here we define two very basic button components. TheButton
component has a few base styles, which could be further extended.ButtonSmall
has a predefined text component and therefore accepts button text as a separate prop. Additionally we export everything fromButtons.js
insideindex.js
as a convenience. This will ensure a single point of export for each package, particularly helpful when there are multiple files per package. Now let's try these new components out in the playground.
// packages/buttons/docs/buttons.mdx --- name: Buttons menu: Components --- import { Playground } from 'docz'; import { Button, ButtonSmall } from '../src/index'; # Buttons ## Base button <Playground> <Button>Test</Button> </Playground> ## Small button <Playground> <ButtonSmall text='Click me'/> </Playground>
Navigating back tolocalhost:3000
we can confirm that the buttons work as expected. With that we have a properly documented, functioning component library, which can be easily extended.
Deploying the docs and publishing packages
So far the we have been focusing mostly on development side of the component library, however there are a few other important steps that need to happen before the library becomes usable.
Publishing packages
To publish all the packages that have been changed since the last publishing took place (and after they have been transpiled withBabel), we can uselerna publish
command. It will prompt to specify versioning for each package before publishing them. The version can be specified directly with the publish
command, which will apply the same versioning to all the changed packages and will skip the prompts, e.g.lerna publish minor
. For publishing to work, aregistry
needs to be added inlerna.json
.
"command":{"publish":{"registry":"https://mypackageregistry/"}}
Building the docs and serving them
Docz comes with a few built-in scripts that make it easier to view and deploy the documentation. It can be built and served locally by runningyarn docs build && yarn docz serve
. To deploy the documentation online Docz's site has a handy example ofdoing it with Netlify. After Netlify site has been setup, deploying is easy via running netlify deploy --dir .docz/dist.
If you want to have a look at the boilerplate code for the component library, it's available on myGithub.
Top comments(2)

- LocationTallinn, Estonia
- WorkHead of Engineering at Jobbatical
- Joined
Thanks Alex, it was really helpful. I was thinking about exploring yarn workspace and lerna. Is it possible to push this to npm as library.
May be a next article to automate that 😉

- LocationHelsinki
- WorkFrontend developer
- Joined
Thank you for the feedback! The boilerplate is actually available on my Github, but I forgot to link it to the article, I'll do it now. Thanks for reminding ;)
For further actions, you may consider blocking this person and/orreporting abuse