Building a UI Library and Publishing to npm - A Developer’s Guide
Authored_by: Basant Rai

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.
Step 1: Project Setup
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";Step 2: Configuring the Bundler
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,
});
Step 3: Adding Tailwind + CSS Bundle
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";
Step 4: Preparing for npm Publish
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:
- Use peerDependencies so you don’t bundle React.
- The files field keeps your package clean.
Step 5: Publishing to npm
npm login
# Build and publish
npm build
npm publish --access publicStep 6: Using Your Library
pnpm add @my-scope/ui
// Import
import { Button } from "@my-scope/ui";
import "@my-scope/ui/dist/index.css";
export default function App() {
return <Button />;
}Common Gotchas
Here are a few mistakes I made that you can avoid:
- Forgetting to mark React as external results in two React versions running.
- Publishing without including CSS components look broken.
- Not testing in a fresh project always do this before publishing.
Final Thoughts
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.