Bayan Bennett

Making a UI Library with React & TypeScript

React JS
TypeScript
Accompanying video

In this project we're going to create the simplest UI library imaginable. It's going to be written using React and TypeScript and published as a package on NPM.

npm init

Create an empty project and run npm init. Enter in the requested information when prompted.

Next, install any necessary dependencies. For my project, all I need is React and TypeScript.

tsconfig

Inside of the root of the repository, add a tsconfig.json file. I always recommend de-duplicating work and that means using @tsconfig/recommended. My tsconfig.json file looks like this:

{
  "extends": "@tsconfig/recommended/tsconfig.json"
}

Although this is a great start for tsconfig, look at all the different options and choose the ones that you think is our best for your project. In my case, I am using React, so I'll need to set the jsx compiler option.

jsx preserve

TypeScript ships with three jsx modes:

  • preserve
  • react
  • react native

I'll be using "preserve" as I'm making a library that's going to be used somewhere else and I don't want to transpile into JS like below

❌    <div/> ➡ React.createElement("div")

Making a UI Component

Next, I'm going to create src/index.ts with a simple component (src/Button.tsx) that doesn't do anything.

export const Button = () => null

And export that component from my index.ts file:

export * from "./Button"

Styled Components

I need a way of styling a component, and as the name suggests, styled-components is a library that uses SASS-like syntax to generate React components. It's something that I've enjoyed using frequently. I'm going to install styled-components and its type library

npm i styled-components
npm i -D @types/styled-components

I then replaced my button functional component with this styled component syntax for a button

export const Button = styled.button``

This styled button component doesn't really do anything different than this code below:

export const Button = (props) => <button {...props} />

`styled-componentsa gets much more powerful when you start putting the SASS into it. In this case I'm just going to make a simple example and just make the background color green:

export const Button = styled.button`
  background-color: green
`

Making a Package

Unbelievably, we've created our first UI library. A UI library isn't really something that is usable until you make a package out of it. First, open package.json and change the main field to look inside the src directory:

"main": "src/index.ts",

Secondly, what needs to be packed is specified by in package.json:

"files": ["src/**/*"],

The src/**/* is a glob pattern that matches everything inside the srcdirectory. Next thing we do is to run the npm pack command and this will zip everything up into an archive, which represents what will be delivered to users when they run npm install. This is inside the generated tar zip file coradion-ui-1.0.0.tar:

src
| Button.tsx
| index.ts
package.json

Although there are a few more things inside of our repository, the only things that are packed is the src directory and the package.json file. There are a few things that are always included by default like the package.json and a readme. We don't have a readme so let's add one now in the root of the project README.md:

# @Coradion/ui
This is a readme

When I run the npm pack command again, inside the package there's now a readme file:

src
| Button.tsx
| index.ts
package.json
README.md

NPM Link

To test a package without publishing it to the NPM package repository use the npm link command. Inside your package run:

npm link

Inside the project that is going to use your package run the same command, but with the name of the package:

npm link @coradion/ui

Inside our project node_modules folder you can see we have a new folder called @coradion and it has a bin file called ui

The package can now be imported in your project like this:

import { Button } from "@coradion/ui"

Why Are Other Packages So Complicated?

Looking at a library like Material UI and there are an overwhelming number of files and dependencies. The main package.json alone is 227 lines long with 117 dependencies!

Even looking at tooling, they have Lerna, Rollup, TypeScript, and Webpack. It's easy to look at all that complexity and assume it's difficult to create your own package. Yet, what was created in this post is a suspiciously simple example of a functional package with a fraction of the complexity.

TL; DR

I start off with a fresh project and make a UI library (that only has one basic component) using TypeScript, ReactJS, and Styled Components. I also make a basic NPM package that can be used locally to test the UI library.

© 2022 Bayan Bennett