

As Frontend developers, we’re constantly rebuilding the same components — buttons, modals, form inputs — and reusing them across projects. Sure, there are plenty of UI libraries out there, but customizing them is often complicated and time-consuming. Copy-pasting works, but it’s messy, templates can help, but they’re limited and hard to maintain across multiple projects. I wanted something faster, easier to customize, and maintainable — like magic: one command and all the useful components are in the project. A single source of truth I could improve once and use everywhere.
That’s why I decided to build my own basic UI component library and publish it to npm and I was shocked to discover that publishing it to npm was far easier than I expected.
Start with a clean folder and initialize the project:
npm init
npm add react react-dom
npm add -D typescript tsup tailwindcss postcss autoprefixer
A simple folder structure looks like this:
/src
/components
Button.tsx
Modal.tsx
index.tsThe index.ts file is where you’ll export all your components:
export * from "./components/Button";
export * from "./components/Modal";I chose tsup because it’s fast, minimal, and works really well with TypeScript.
Here’s a sample config:
// tsup.config.ts
import { defineConfig } from "tsup";
export default defineConfig({
entry: ["src/index.ts"],
format: ["cjs", "esm"],
dts: true,
sourcemap: true,
minify: true,
clean: true,
external: ["react", "react-dom"],
css: true,
});
Here’s a quick example of a simple button:
// src/components/Button.tsx
export const Button = () => (
<button className="px-4 py-2 rounded bg-blue-500 text-white">
Click Me
</button>
);
To make sure your styles work when someone installs your package, bundle them into a dist/index.css file. Don’t forget to tell users to import it:
import "@my-scope/ui/dist/index.css";
Update your package.json:
{
"name": "@my-scope/ui",
"version": "1.0.0",
"main": "dist/index.cjs",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"files": ["dist"],
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
}
Notes:
npm login
# Build and publish
npm build
npm publish --access publicpnpm add @my-scope/ui
// Import
import { Button } from "@my-scope/ui";
import "@my-scope/ui/dist/index.css";
export default function App() {
return <Button />;
}Here are a few mistakes I made that you can avoid:
Building a UI library isn’t just about shipping buttons and modals. It’s about learning how packaging, bundling, and distribution work in real projects. Even if you only have two or three components, publishing them to npm is a great experience — and who knows, maybe other developers will use them too.
So if you’ve been building reusable components, why not package them up? You might be surprised how much you’ll learn along the way.