DEV Community

Cover image for Develop a Full-Fledged Component Library with React, just like Material UI
Tapajyoti Bose
Tapajyoti Bose

Posted on • Edited on

Develop a Full-Fledged Component Library with React, just like Material UI

Always wondered how component libraries work in React? Want to create a library of your own, but the task seems too daunting? Fret no more! This article will teach you just that!

Let's kick things off!

roll-up-sleeve

Initializing Project

Initialize a new project with

npm init
Enter fullscreen mode Exit fullscreen mode

Add the dependencies using:

npm i react react-dom
Enter fullscreen mode Exit fullscreen mode

Rename the dependencies in package.json to peerDependencies, which informs npm of the packages your project relies on.

Adding Storybook

Now comes the most tedious part of the setup.

Since you would need to test out the components you build, you could create a web app with all the components or use a tool like Storybook to easily test out your components.

Initialize a Storybook project with

npx sb init
Enter fullscreen mode Exit fullscreen mode

This will automatically detect the project type, add the necessary packages & scripts.

Move the /src/stories folder to the root of your project (/stories) and update /.storybook/main.js with:

module.exports = {
  // ...
  stories: [
    "../stories/**/*.stories.mdx",
    "../stories/**/*.stories.@(js|jsx|ts|tsx)",
  ],
};
Enter fullscreen mode Exit fullscreen mode

You can now start up the storybook project with

npm run storybook
Enter fullscreen mode Exit fullscreen mode

To add CSS Modules support to the project, install the following:

npm i -D @storybook/addon-postcss storybook-css-modules-preset
Enter fullscreen mode Exit fullscreen mode

Update the /.storybook/main.js configuration with:

module.exports = {
  // ...
  addons: [
    // ...
    "@storybook/addon-postcss",
    "storybook-css-modules-preset",
  ],
};
Enter fullscreen mode Exit fullscreen mode

NOTE: Noticed that storybook's dependencies are conflicting with React 18, if you get an error while starting up the storybook, try downgrading to React 17.

Create a Component

Now it's finally time to create a component.

/* /src/Button/button.module.css */
.storybookButton {
  font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-weight: 700;
  border: 0;
  border-radius: 3em;
  cursor: pointer;
  display: inline-block;
  line-height: 1;
}

.storybookButtonPrimary {
  color: white;
  background-color: #1ea7fd;
}

.storybookButtonSecondary {
  color: #333;
  background-color: transparent;
  box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
}

.storybookButtonSmall {
  font-size: 12px;
  padding: 10px 16px;
}

.storybookButtonMedium {
  font-size: 14px;
  padding: 11px 20px;
}

.storybookButtonLarge {
  font-size: 16px;
  padding: 12px 24px;
}
Enter fullscreen mode Exit fullscreen mode
// /src/Button/Button.js
import React from "react";

import classes from "./button.module.css";

const Button = ({ primary, backgroundColor, size, label, ...props }) => {
  const mode = primary
    ? classes.storybookButtonPrimary
    : classes.storybookButtonSecondary;
  return (
    <button
      type="button"
      className={[
        classes.storybookButton,
        classes[`storybookButton${size}`],
        mode,
      ].join(" ")}
      style={backgroundColor && { backgroundColor }}
      {...props}
    >
      {label}
    </button>
  );
};

export default Button;
Enter fullscreen mode Exit fullscreen mode
// /src/Button/index.js
export { default } from "./Button";
Enter fullscreen mode Exit fullscreen mode

Since we are working on a component library, it is crucial to export the components in the main index.js file.

// /src/index.js
export { default as Button } from "./Button";
Enter fullscreen mode Exit fullscreen mode

To test out the component, let's add a story. Make sure you remove the default stories that were added by Storybook.

// /stories/Button.stories.js
import React from "react";

import { Button } from "../src";

export default {
  title: "Basics/Button",
  component: Button,
  argTypes: {
    backgroundColor: { control: "color" },
  },
};

const Template = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: "Button",
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: "Button",
};

export const Large = Template.bind({});
Large.args = {
  size: "Large",
  label: "Button",
};

export const Small = Template.bind({});
Small.args = {
  size: "Small",
  label: "Button",
};
Enter fullscreen mode Exit fullscreen mode

Now you can run storybook and visit http://localhost:6006/?path=/story/basics-button--primary to checkout & modify the component on the fly.

Feel free to add as many components and stories as your library requires!

Building Project

What good is a project, that we cannot share with the world? Let's build the project & distribute it on npm!

Install Rollup with

npm i -D rollup rollup-plugin-peer-deps-external rollup-plugin-postcss rollup-plugin-terser @rollup/plugin-babel @rollup/plugin-commonjs @rollup/plugin-node-resolve
Enter fullscreen mode Exit fullscreen mode

Setup Rollup Configuration

// /rollup.config.js
import resolve from "@rollup/plugin-node-resolve";
import { babel } from "@rollup/plugin-babel";
import external from "rollup-plugin-peer-deps-external";
import { terser } from "rollup-plugin-terser";
import postcss from "rollup-plugin-postcss";

export default {
  input: "src/index.js",
  output: [
    {
      file: "dist/index.js",
      format: "cjs",
    },
    {
      file: "dist/index.es.js",
      format: "es",
      exports: "named",
    },
  ],
  plugins: [
    postcss({
      plugins: [],
      minimize: true,
    }),
    babel({
      exclude: "node_modules/**",
      presets: ["@babel/env", "@babel/preset-react"],
      babelHelpers: "bundled",
    }),
    external(),
    resolve(),
    terser(),
  ],
  external: ["react", "react-dom"],
};
Enter fullscreen mode Exit fullscreen mode

Add the script to build the files:

// /package.json
{
  // ...
  "scripts": {
    // ...
    "build": "rollup -c"
  }
}
Enter fullscreen mode Exit fullscreen mode

Now you can build out the project with

npm run build
Enter fullscreen mode Exit fullscreen mode

Now you can publish the project on npm! Just make sure it has a unique package name.

Wrapping Up

Material UI is a very mature library that has been around for years. Olivier definitely deserves a mention for creating such an outstanding library, used by even the humongous tech corporations!

If you want to create a library that truly competes with Material UI, you should be prepared to put in decades of grueling work first.

Best of luck!

thumbs-up

Finding personal finance too intimidating? Checkout my Instagram to become a Dollar Ninja

Thanks for reading

Need a Top Rated Front-End Development Freelancer to chop away your development woes? Contact me on Upwork

Want to see what I am working on? Check out my Personal Website and GitHub

Want to connect? Reach out to me on LinkedIn

I am a freelancer who will start off as a Digital Nomad in mid-2022. Want to catch the journey? Follow me on Instagram

Follow my blogs for Weekly new Tidbits on Dev

FAQ

These are a few commonly asked questions I get. So, I hope this FAQ section solves your issues.

  1. I am a beginner, how should I learn Front-End Web Dev?
    Look into the following articles:

    1. Front End Development Roadmap
    2. Front End Project Ideas

Top comments (3)

Collapse
 
funnypan profile image
panfan

how to solve images & svgs used by each components?

I have tried some methods.but nothing done.

Collapse
 
ruppysuppy profile image
Tapajyoti Bose

Typically you shouldn't use rastor images in a ui library. To use vector images like svgs, create components for them (since svg elements are valid html & jsx elements)

Collapse
 
irzhywau profile image
Irzhy Ranaivoarivony • Edited

there is dedicated plugin on rollup for svg ang other images, you probably need to check @rollup/plugin-image and rollup-plugin-svg-import packages.
Though there is a caveat, especially for images, they will be converted into base64 so your library will be larger