At RateSetter we have a number of front-end projects that are worked on by different project teams. Each of these projects are designed by our internal designer and had a lot common with them (same inputs, buttons designs etc). Up until now, we had no shared style-sheet or components, so each of these components were created from scratch or copy pasted over each time. I saw a real need for a RateSetter component library. This component library would enable teams to pull down an NPM package, import components, provide some props and have their RateSetter styled component ready to use.

I strongly believed the component would help speed up front-end development by removing the need to write/copy paste components over to new projects and also great for me to learn how to create, managed and get people using a component library used in production throughout multiple engineering teams and projects - so I created RateSetter's component library!

Component Library Overview

There were a number of requirements I had for the component library. Being:

  • Must be React
  • Must house a number of components
  • Must use Sass
  • Must use TypeScript
  • Each component must be thoroughly tested (using Jest/Enzyme)
  • Must be able to bundle the components and be published to an internal NPM registry as a single library
  • Components should be able to be displayed and interacted with on an internal website

Just give me the component library skeleton

Sometimes it's just easier to clone a repository as opposed to following along with the article creating everything from scratch, I get it. For your convenience I've created a repository which has all the configuration needed for creating your own component library:

Contribute to HarveyD/react-component-library development by creating an account on GitHub.

Clone it then give it a star if it helps you out :). If you feel like it can be improved, please raise a pull request!

Component Library Research

Other Component Library Tools

Before I created our own component library, I had a look around to see what other React component libraries were out there and whether we could use them. Turns out there's quite a few great libraries such as:

Our designer had created the designs for the RateSetter components with a specific style that none of these libraries could easily achieve without going into the internals of the components and altering them. So I decided it would be easier to create our own components.

Other Component Library Tools

I then had a look around at a number of React project skeletons I could use to bootstrap the component library. But I eventually decided to just create the Component Library from scratch. Here are the libraries I considered:

All of these libraries had excellent documentation, support and a number of features that made it easy to get different flavours of React projects up and running. But there was always an aspect that wasn't able to be configured easily to meet my requirements (since they abstracted config away). For example, I wanted to use Typescript, but NWB didn't have great support for it. I also wanted to use Sass, but I found it tricky to get Create React Library transforming and bundling the .scss files.

Due to all the above not 100% matching the requirements I had for the component library - I decided to create all the config from scratch!

Custom Component Library Choices


My decision for a JavaScript module bundler to use was between Webpack and Rollup. I decided to use Rollup for the component library after researching what both Webpack and Rollup are good and bad at. This article goes into depth about these differences. The take-home is that:

Use webpack for apps, and Rollup for libraries

Other articles you can read about these differences are here and here.


I found out about Storybook in an interview I had with a web agency a few years ago. They explained they were using Storybook to help them create, display and experiment with their React components. After the interview, I did my own research and experimentation with Storybook and fell in love with the tool. Storybook felt like a perfect fit for the RateSetter component library.

Storybook provides a sand-boxed environment for your front-end project that helps you to develop your components. The sandbox environment encourages engineers to create components that aren't tied logic within your application (for example, heavily coupled with a Redux store). This results in components that are more generic and more re-usable.

Storybook also has the ability to be exported as a static webpage. This allows everyone in your organisation to see how components look/interact without having to clone and setup the repository locally - perfect for product managers and designers.

Creating the component library

Since we're creating our  project from scratch, we need to setup the structure. So after setting up npm (npm init) and also setting up git (git init).

Since we're creating a React component library, we need to install React:

npm i --save react react-dom
npm i --save-dev @types/react

The react and react-dom dependencies should be configured as Peer Dependencies. Having React as a peer dependency will mean that once our library is installed, React won't be installed as a dependency for our project. Instead, NPM will output a warning saying React is required if it hasn't been installed alongside our library.

Move react and react-dom from under dependencies to peerDependencies:

  "peerDependencies": {
    "react": "^16.12.0",
    "react-dom": "^16.12.0"

Now let's create the skeleton of our library by creating the following files and directories:


In test-component.tsx we will have a very simple component:

import React from "react";

import './test-component.scss';

interface IProps {
    theme: 'primary' | 'secondary';

const TestComponent: React.FC<IProps> = ({ theme }) => (
  <div className={`test-component test-component-${theme}`}>
    <h1 className="heading">I'm the test component</h1>
    <h2>Made with love by Harvey</h2>

export default TestComponent;

And in test-component.scss we will have:

.test-component {
    background-color: white;
    border: 1px solid black;
    padding: 16px;
    width: 360px;
    text-align: center;
    .heading {
        font-size: 64px;

    &.test-component-secondary {
        background-color: black;
        color: white;

index.tsx will be used as the entry point for Rollup. Within index.tsx we will need to import and also export TestComponent, this is so when we bundle and export the library - all the components will be too. It will look like:

import TestComponent from "./test-component/test-component";

export { TestComponent };

We will leave test-component.stories.js and test-component.test.tsx empty for now.

Adding TypeScript

Run npm i -D typescript and in tsconfig.json add:

  "compilerOptions": {
    "outDir": "build",
    "module": "esnext",
    "target": "es5",
    "lib": ["es6", "dom", "es2016", "es2017"],
    "sourceMap": true,
    "allowJs": false,
    "jsx": "react",
    "declaration": true,
    "moduleResolution": "node",
  "include": ["src","images.d.ts"],
  "exclude": ["node_modules", "build"],
  "esModuleInterop": true

Alternatively, you can run tsc --init to generate this file and provide your own config.

Adding Rollup

Create rollup.config.js and inside we'll put:

import typescript from "rollup-plugin-typescript2";
import sass from "rollup-plugin-sass";
import commonjs from "rollup-plugin-commonjs";
import external from "rollup-plugin-peer-deps-external";
import resolve from "rollup-plugin-node-resolve";

import pkg from "./package.json";

export default {
  input: "src/index.tsx",
  output: [
      file: pkg.main,
      format: "cjs",
      exports: "named",
      sourcemap: true
      file: pkg.module,
      format: "es",
      exports: "named",
      sourcemap: true
  plugins: [
      browser: true
      rollupCommonJSResolveHack: true,
      exclude: "**/__tests__/**",
      clean: true
      include: ["node_modules/**"],
      exclude: ["**/*.stories.js"],
      namedExports: {
        "node_modules/react/react.js": [
        "node_modules/react-dom/index.js": ["render"]
      insert: true

This is the config file that I conjured up after browsing a number of other articles in addition to experimentation to get it working right for the requirements for the library. The main things to take note of are the addition of the Rollup TypeScript Plugin and the Rollup Sass Plugin.

For this Rollup config to work, we need to install Plugin in addition to some Rollup plugins to get our component library working nicely:

npm i -D rollup rollup-plugin-typescript2 rollup-plugin-sass rollup-plugin-commonjs rollup-plugin-node-resolve rollup-plugin-peer-deps-external

Now in package.json we have to add a couple things, first we have to add config that's used in our rollup.config.js file, under version put:

  "main": "build/index.js",
  "module": "build/",
  "jsnext:main": "build/",
  "files": [

Yes, we could have hard-coded these values within rollup.config.js, but I think it was better to add them here and import them in the config file. Now we need to create a script that will run our Rollup bundling process:

"scripts": {
  "build" "rollup -c"

Now by running rollup -c you should see Rollup perform some magic and create a /build folder that will contain all the assets in our component library, ready for deployment to an NPM registry:

Note: you might want to create a .gitignore file and add node_modules and build to it.

Adding Storybook

The first step to add Storybook was to run the automatic setup command (documented here):

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

Since we are using TypeScript and Sass for our components, there's some additional config we have to add to get Storybook working nicely.

Navigate to the newly created .storybook directory and create a webpack.config.js file. In this file add:

const path = require('path');

module.exports = async ({ config, mode }) => {
    test: /\.scss$/,
    use: ['style-loader', 'css-loader', 'sass-loader'],
    include: path.resolve(__dirname, '../'),

    test: /\.(ts|tsx)$/,
    loader: require.resolve('babel-loader'),
    options: {
      presets: [['react-app', { flow: false, typescript: true }]],
  config.resolve.extensions.push('.ts', '.tsx');

  return config;

Storybook uses Webpack and if we create this config file, Storybook will use it. We need to install some Webpack loaders to get the above config working:

npm i -D @babel/core babel-loader sass-loader

Since we are placing our stories within the component's folders and not within the storybook directory, we'll need to change the config. Within .storybook/config.js, update the file to be:

import { configure } from '@storybook/react';

// automatically import all files ending in *.stories.js
    require.context('../src', true, /\.stories\.js$/),

Also delete the stories directory which was generated by the Storybook CLI as we already created the story file alongside the component itself.

Now, we have to create stories for our TestComponent. Open to src/test-component/test-component.stories.js and place:

import React from "react";
import TestComponent from './test-component';

export default {
  title: "TestComponent"

export const Primary = () => <TestComponent theme="primary" />;

export const Secondary = () => <TestComponent theme="secondary" />;

This is a very basic story, showing the two variants of our component.

Storybook should have created a new entry in package.json scripts called, storybook, so run npm run storybook and Storybook will do it's magic and load up your components at localhost:6006.  Storybook should look like:

Adding Jest/Enzyme

Maintaining a high level of test coverage on components is extremely important for the library. We need to have confidence that when we alter our components that we won't be breaking how the component is being expected to behave in another project. For our testing, we will be using Jest and Enzyme.

Install Jest, Enzyme and Enzyme helpers (and associated types):

npm i --save-dev jest ts-jest enzyme enzyme-adapter-react-16 @types/jest @types/enzyme-adapter-react-16 @types/enzyme enzyme-to-json identity-obj-proxy

In jest.config.js add:

module.exports = {
  roots: ["./src"],
  setupFiles: ["./setupTests.ts"],
  moduleFileExtensions: ["ts", "tsx", "js"],
  testPathIgnorePatterns: ["node_modules/"],
  transform: {
    "^.+\\.tsx?$": "ts-jest"
  testMatch: ["**/*.test.(ts|tsx)"],
  moduleNameMapper: {
    // Mocks out all these file formats when tests are run
    "\\.(css|less|scss|sass)$": "identity-obj-proxy"
  snapshotSerializers: ["enzyme-to-json/serializer"]


And in setupTests.ts add:

import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

And add two new scripts in package.json to run the tests:

"test": "jest --watch",
"test:once": "jest",

Then in test-component.test.tsx create two simple tests:

import React from "react";
import { shallow } from "enzyme";

import TestComponent from "./test-component";

describe("Test Component", () => {
  let props: any;

  beforeEach(() => {
    props = {
      theme: "primary"

  const renderWrapper = () => shallow(<TestComponent {...props} />);

  describe("Snapshots", () => {
    it("should match snapshots as primary themed", () => {

    it("should match snapshots as secondary themed", () => {
      props.theme = "secondary";

After running npm run test you should see Jest run and output:

Indicating we have set it up correctly!

Adding more components

The way we've structured our project allows for any number of components to be added. Simply copy the test-component folder, rename and then create your new component!

Just remember to add the new component to index.tsx otherwise it won't be picked up and bundled by Rollup.

Further Steps

Setting up a Private NPM Registry, CI Pipeline and Consuming the Library

The next steps to get this component library out and being used was to setup a private NPM registry and pipeline which automatically publishes to it. I'll be covering this aspect in another blog post which you can read at:

Maintaining Code Quality in your Component Library

As this component library will likely be contributed by other engineers on your team, you'll probably want to maintain a high level of code quality and formatting. Follow along with: to help you create automation to help achieve this.