How to Create a React Component Library
Initialize the Package
mkdir my-component-library
cd my-component-library
npm init
You will be prompted for input about the package. You can answer with the default values and update the package settings later.
Install React
npm install react --save-dev
Install & Configure TypeScript
npm install typescript @types/react --save-dev
Generate a TypeScript config file using the following command:
npx tsc --init
Configure the TypeScript settings:
// tsconfig.json
{
"compilerOptions": {
// Default
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
// Added/changed
"target": "es5",
"jsx": "react-jsx",
"module": "ESNext",
"declaration": true,
"declarationDir": "types",
"sourceMap": true,
"outDir": "dist",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"emitDeclarationOnly": true
}
}
Create a Component
In the package directory, create a src
directory with the structure shown below:
├── my-component-library
│ ├── node_modules
│ ├── src
│ │ ├── components
│ │ │ ├── Button
│ │ │ │ ├── Button.css
│ │ │ │ ├── Button.tsx
│ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── .gitignore
│ ├── package.json
│ ├── README.md
│ └── tsconfig.json
/* src/components/Button/Button.css */
.button {
font-size: 100px;
}
// src/components/Button/Button.tsx
import "./Button.css";
interface ButtonProps {
label: string;
}
export const Button = ({ label }: ButtonProps) => {
return <button className="button">{label}</button>;
};
// src/components/Button/index.ts
export { Button } from "./Button";
// src/components/index.ts
export { Button } from "./Button";
// src/index.ts
export * from "./components";
Install & Configure Rollup
Install Rollup and plugins
npm install rollup @rollup/plugin-node-resolve @rollup/plugin-typescript @rollup/plugin-commonjs rollup-plugin-dts rollup-plugin-postcss rollup-plugin-peer-deps-external --save-dev
- @rollup/plugin-node-resolve - For Rollup to locate and bundle third-party dependencies in node_modules. The dependencies meant here are the dependencies listed in your package.json file.
- @rollup/plugin-typescript - To help integrate with TypeScript in an easier fashion. Covers things like transpiling TypeScript to JavaScript.
- Note: both
typescript
andtslib
are peer dependencies of this plugin that need to be installed separately.
- Note: both
- @rollup/plugin-commonjs - For Rollup to convert CommonJS modules into ES6, so that they can be included in a Rollup bundle. It is typically used along with @rollup/plugin-node-resolve, to bundle the CommonJS dependencies.
- rollup-plugin-dts - Rollup your .d.ts definition files
- rollup-plugin-css - To integrate with PostCSS
- rollup-plugin-peer-deps-external - Excludes peer dependencies from the bundle
@rollup/plugin-typescript has an additional dependency that needs to be installed:
npm install tslib --save-dev
Create Rollup configuration file rollup.config.mjs
in the project root directory:
// rollup.config.mjs
import commonjs from "@rollup/plugin-commonjs";
import resolve from "@rollup/plugin-node-resolve";
import typescript from "@rollup/plugin-typescript";
import dts from "rollup-plugin-dts";
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import postcss from "rollup-plugin-postcss";
import packageJson from "./package.json" assert { type: "json" };
export default [
{
input: "src/index.ts",
output: [
{
file: packageJson.main,
format: "cjs",
sourcemap: true,
},
{
file: packageJson.module,
format: "esm",
sourcemap: true,
},
],
plugins: [
// Preferably set as first plugin.
peerDepsExternal(),
resolve(),
commonjs(),
typescript({ tsconfig: "./tsconfig.json" }),
postcss(),
],
},
{
input: "dist/esm/types/index.d.ts",
output: [{ file: "dist/index.d.ts", format: "esm" }],
plugins: [dts()],
external: [/\.css$/],
},
];
Package Settings
// package.json
{
"name": "my-component-library",
"version": "1.0.0",
"description": "My React component library",
"author": "Name",
"license": "MIT",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"files": ["dist"],
"types": "dist/index.d.ts",
"scripts": {
"rollup": "rollup -c"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^24.1.0",
"@rollup/plugin-node-resolve": "^15.0.2",
"@rollup/plugin-typescript": "^11.1.0",
"@types/react": "^18.2.6",
"react": "^18.2.0",
"rollup": "^3.21.5",
"rollup-plugin-dts": "^5.3.0",
"rollup-plugin-postcss": "^4.0.2",
"tslib": "^2.5.0",
"typescript": "^5.0.4"
},
"peerDependencies": {
"react": "^18.2.0"
}
}
"main": "dist/cjs/index.js"
- Path of the CommonJS-style module"module": "dist/esm/index.js
- Path of the ECMAScript-style module"files": ["dist"]
- Array of file patterns that describes the entries to be included when your package is installed as a dependency."types": "dist/index.d.ts"
- Path of the bundled declaration file."scripts.rollup": "rollup -c"
- Script to create a bundle with Rollup using the config file"peerDependencies" : { "react": "x.x.x" }
- The apps using the library will have React installed
Create the Bundle
npm run rollup
Publish to GitHub
Create a .gitignore file:
# .gitignore
dist
node_modules
Create a GitHub repostiory and push the code.
Add the following to the package.json configuration:
// package.json
{
"name": "GITHUB_USERNAME/REPO_NAME",
"publish": {
"registry": "https://npm.pkg.github.com/GITHUB_USERNAME"
}
...
}
Create npm config file .npmrc
in home directory (e.g. C:/Users/username/.npmrc
)
registry=https://registry.npmjs.org/
@YOUR_GITHUB_USERNAME:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=YOUR_AUTH_TOKEN
To get your auth token:
- Go https://github.com/settings/tokens.
- Click "Generate new token" button (classic mode)
- Check the "write:packages" scope
- Copy the token.
Publish the package to your GitHub by running the command:
npm publish
Go to your GitHub profile page and click the Packages tab to see the package you just published.
Using the Package Locally
You might want to keep your library private. You can import your local package into one of your projects by simply adding it as a dependency in the project:
npm i ./path-to/my-component-library
This will add your local package as a dependency in package.json:
// my-frontend-app/package.json
{
...
"dependencies": {
"my-component-library": "file:./path-to/my-component-library",
...
}
}
You will now be able to import Button from "my-component-library".
If changes are made to the component library, recreate the package bundle by running the npm run rollup
command in the library's root directory. The library changes will then take effect in the consumer app because a link was created to the local package when it was installed as a dependency.
Add Tailwind (Optional)
npm install tailwindcss postcss autoprefixer --save-dev
npx tailwindcss init -p
Add the paths to all of your template files in your tailwind.config.js
file:
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
Insert Tailwind's base
, components
, and utilities
styles into your CSS
/* src/styles/tailwind.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
Import the CSS into the components entry point
// src/components/index.ts
import "../styles/tailwind.css";
export { Button } from "./Button";
You can now add Tailwind styles to your components that will be included in the rollup build.
Add Storybook
Install Storybook:
npx storybook@latest init
When asked if you would like to run the missing-babelrc
migration on your project, answer "Y".
Answer "Y" to the three follow-up questions.
Install the following addons:
npm install @storybook/addon-a11y @storybook/addon-styling --save-dev
- @storybook/addon-a11y - Test component compliance with web accessibility standards
- @storybook/addon-styling - Enable toggling between themes and other styling features
Then include the addons in your .storybook/main.ts file:
// .storybook/main.ts
import type { StorybookConfig } from "@storybook/react-webpack5";
const config: StorybookConfig = {
stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [
"@storybook/addon-a11y",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
"@storybook/addon-links",
{
// https://storybook.js.org/recipes/tailwindcss
name: "@storybook/addon-styling",
options: {
// Check out https://github.com/storybookjs/addon-styling/blob/main/docs/api.md
// For more details on this addon's options.
postCss: true,
},
},
],
framework: {
name: "@storybook/react-webpack5",
options: {},
},
docs: {
autodocs: "tag",
},
};
export default config;
Provide Tailwind to your stories in the preview.ts
file. Use withThemeByClassName
to add your themes to the Storybook toolbar
// .storybook/preview.ts
import type { Preview } from "@storybook/react";
import "../src/styles/tailwind.css";
const preview: Preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
};
export default preview;
If additional styling changes are needed, create a Storybook stylesheet
and import it into preview.ts
:
/* .storybook/storybook.css */
html {
height: 100% !important;
background: #000000 !important;
color: #ffffff !important;
}
html:not(.dark) > .sb-show-main {
background: #ffffff !important;
color: #000000 !important;
}
.dark > .sb-main-padded {
padding: 0;
}
.dark > .sb-show-main > #storybook-root {
padding: 1rem;
background: #000000 !important;
color: #ffffff;
}
.sb-show-main.sb-main-padded {
height: 100% !important;
}
#storybook-root {
height: 100%;
}
/* Docs are not configured for dark mode */
#storybook-docs {
background: #ffffff;
color: #000000;
}
Delete the src/stories folder containing example stories.
Create your own stories!