Skip to main content

Command Palette

Search for a command to run...

How I Created a Reusable UI Library for Dashboards with Next.js and Storybook

Custom UI-component library

Updated
6 min read
How I Created a Reusable UI Library for Dashboards with Next.js and Storybook
A

As a Front-end Developer and Digital Artist, I am a creative problem solver with an eye for detail and a passion for pushing boundaries. I bring a unique blend of technical skills and artistic flair to every project, allowing me to create websites and digital designs that not only meet functional requirements but also capture the imagination of audiences. I am always exploring new technologies and design trends to stay ahead of the curve and bring fresh ideas to my work. I have a natural curiosity and a willingness to experiment that enables me to find innovative solutions to complex problems. When I'm not immersed in code or creating digital art, you can find me exploring the great outdoors or pursuing my other interests, such as playing music or reading books. My diverse range of interests and experiences allows me to bring a unique perspective to every project and create designs that truly stand out. Overall, I am a dynamic and multifaceted professional who is passionate about creating exceptional digital experiences that leave a lasting impression.

Ever wondered how developers create reusable UI libraries that just work across any project? That’s exactly what I wanted to figure out, and now, I’ve done it.

This isn’t a tutorial written from a pedestal, this is a story. My story.

I was juggling freelance projects, building BrewMyAgent with friends, and trying not to go insane rewriting the same damn components again and again. Button. Card. Toast. Table. Over. And. Over.

So one weekend, I said: Enough. I’m building my own UI library. One that’s battle-tested for dashboards, powered by shadcn/ui, runs on Next.js, and is pretty enough to not make my eyes bleed.

🌱 The Problem: Déjà Vu in Code

If you’ve worked on more than two frontend projects, you’ve seen this:

  • Same primary button logic

  • Same layout hacks

  • Same toast popups that never feel just right

In one of my projects, I even had two slightly different modal components doing the exact same thing. 🤦‍♂️ That was my breaking point.

I realized I was wasting hours rebuilding things I’d already built. Not because I wanted to, but because I had no clean, portable version of those components.

So I set out with one goal: Build once. Reuse forever.

🎯 What I Wanted (and Didn't Want)

I didn’t want a huge design system. I didn’t want to spend weeks naming color tokens.

I just wanted:

  • Clean, composable dashboard components

  • Easy dark/light support

  • Easy to install via npm

  • Playable in Storybook

  • And honestly... something that felt like me

🛠️ The Stack I Committed To (Explained)

Here’s what I chose and why:

  • Next.js - A powerful React framework. It supports routing, SSR (server-side rendering), and works great with both static and dynamic content.

  • shadcn/ui - A component collection built using Radix Primitives and styled with Tailwind. It’s opinionated yet customizable.

  • Radix UI Primitives - These are low-level building blocks for building accessible UI components. Think of them like legos without color.

  • TailwindCSS - A utility-first CSS framework that lets you style components directly within the JSX.

  • tsup - A bundler that handles building your library into both ESM (ES Modules) and CJS (CommonJS). It also auto-generates type declarations (.d.ts).

  • Storybook - An isolated playground to preview, test, and document components.

  • npm - The registry for publishing your component library and installing it in other projects.

🔍 Not sure what ESM and CJS mean? Here's a quick guide - they’re just different module systems used in JavaScript.

🔨 Step 1: Build the Project

Start with a solid Next.js app:

npx create-next-app@latest agent-ui --typescript --app
cd agent-ui

Then initialize shadcn:

npx shadcn@latest init

It will ask about Tailwind setup, project directory structure, and whether to include global UI config.

Install some base UI components from shadcn:

npx shadcn@latest add button card badge input

These install accessible, prebuilt UI components styled with Tailwind and Radix.

Tip: Don't add all components blindly. Start with the ones your use case demands.

📁 Step 2: Organize Codebase

Organize files so they’re easy to find and export:

📁 Example structure:

src/
  components/
    layout/      # DashboardShell, Sidebar, Topbar
    ui/          # Atomic components like Button, Badge
  hooks/         # Custom hooks (if needed)
  utils/         # Shared helpers

Create an entry file (src/index.ts) :

// src/index.ts
export * from "./components/ui/AgentCard"
export * from "./components/layout/DashboardShell"
export * from "./components/ui/Button"        //you can export the shadcn components as well
...

This acts as the single entry point for all your exports.

⚙️ Step 3: Set Up Build with tsup

Install tsup:

npm install -D tsup

Create a config file:

// tsup.config.ts
import { defineConfig } from "tsup"

export default defineConfig({
  entry: ["src/index.ts"],
  format: ["esm", "cjs"],
  dts: true,
  external: ["react", "react-dom"],
  clean: true,
})

Add the build script:

"scripts": {
  "build": "tsup"
}

Pitfall: If you see an error with --incremental, make sure you're using the correct tsconfig.json or split a new tsconfig.build.json.

🧪 Step 4: Storybook for Previews

Initialize Storybook:

npx storybook init

Write your first story:

// AgentCard.stories.tsx
import { AgentCard } from "./AgentCard"

export const Default = () => (
  <AgentCard name="Finance Agent" description="Tracks categories" status="active" />
)

Run it:

npm run storybook

Why? This helps you test components in isolation and share with collaborators.

🚀 Step 5: Publish to npm

Update package.json:

"name": "@0xayushm/agentui",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"files": ["dist"],
"exports": {
  ".": {
    "import": "./dist/index.mjs",
    "require": "./dist/index.js"
  }
},
"publishConfig": {
  "access": "public"
}

Then publish:

npm login
npm run build
npm publish --access public

To install it in any project:

npm install @0xayushm/agentui

❗ Challenge Faced: The “use client” Surprise

One issue that tripped me up during the initial integration was the "React is not defined" error when installing the library in another Next.js project.

After digging around, I realized many of my components were using interactivity, things like event handlers, state hooks, or animations, all of which require client-side rendering in Next.js.

So, what was missing?

"use client"

Next.js 13+ introduced the need to explicitly mark a file as a Client Component if it includes interactivity. Without this directive, the file is treated as a Server Component, which doesn't support things like useState, onClick, or even rendering dynamic context.

Fix: I added "use client" at the top of my entry component files (like DashboardShell.tsx, AgentCard.tsx, etc.) to ensure they worked properly when consumed in other projects.

Lesson learned: If your library exports React components that are meant to be interactive, always ensure they're marked with "use client" to avoid runtime errors later.

📦 My UI Library Is Live on GitHub

This project has grown into a real, usable component library, and it's live on GitHub!

👉 Check out the GitHub repo here

The core goal is to make clean, composable, and reusable components for dashboards.
I'm continuously adding new components, think charts, tables, theme switchers, and more.
If you're a developer, designer, or just a UI nerd who wants to contribute, feel free to fork the repo or raise a PR.

Let’s build together, and make UI development more fun and less repetitive. 💻💙

🧠 Common Gotchas & Pro Tips

  • Peer Dependencies: Don’t forget to mark react and react-dom as peerDependencies in package.json.

  • Type Errors: If you face DTS errors, double-check your tsconfig and tsup settings.

  • Component Flexibility: Always forward className and children for better flexibility.

  • Use storybook/addon-docs: It helps auto-generate prop tables and docs.

🏁 Final Thoughts (And Your Turn!)

This project taught me a valuable lesson: Don’t just reuse code. Package it. By creating a reusable UI library, I not only streamlined my workflow but also contributed to the developer community. What started as a weekend experiment is now a tool I use across multiple projects, and it’s open for anyone to use and build upon.

If this post inspired you or helped you in any way:

  • ⭐ Star the GitHub repo to show your support.

  • 💬 Share your feedback or report any issues you encounter.

  • 💡 Fork the repository, remix it, and tag me with your version! I’d love to see how you make it your own.

Let me know what components you’d love to see next - charts, tables, or even AI-powered widgets? Together, we can build clean, efficient UIs and make development more enjoyable and less repetitive. Let’s collaborate and innovate. 🚀

—Ayush