DEV Community

Hasala Abhilasha
Hasala Abhilasha

Posted on • Originally published at designsystemscollective.com on

Scaling Frontend with a Private UI Library


Building a Private UI Library That Actually Scales

How to build, version, and distribute your own design system using Storybook, GitHub Packages, and SemVer.

Let’s be honest. If you are copying and pasting your PrimaryButton component from your old project into your new one, you aren't "reusing code." You are creating technical debt.

When you manage multiple projects a Next.js marketing site, a React dashboard, and maybe a React Native mobile app UI consistency becomes a nightmare. You change a color in one repo, and suddenly your brand identity is fractured across three different apps.

The solution is not better discipline; it’s better infrastructure. You need a Private UI Library.

This isn’t about just moving files into a folder. It’s about treating your UI as a standalone product with its own lifecycle, versioning, and distribution. Here is the deep dive on how to build it right.


Before vs. After (Image is generated with Nano Banana)

1. The “Workshop” Environment: Storybook 📕

You cannot build a robust UI library inside your main application. The context is too messy API calls, Redux stores, and specific business logic will leak into your components. You need a “Clean Room.”

Storybook is the industry standard for UI development. It allows you to build components in total isolation. But don’t just use it to “view” components; use it to stress-test them.

The Workflow

  1. Isolate: Build your component (Button.tsx) without running your backend.
  2. States: Create “Stories” for every possible state: Default, Loading, Disabled, Error, and WithIcon.
  3. Docs: Storybook automatically generates documentation from your TypeScript types and JSDoc comments. This becomes the “manual” for other developers on your team.


https://storybook.js.org/

♿ Accessibility is Not Optional

If you are building a library, you are responsible for accessibility (a11y) across all your projects. Storybook makes this automated.

  • Tool: Install storybook-addon-a11y.
  • How it works: It adds a tab to your Storybook panel that runs real-time audits on your component.
  • The Check: It flags contrast violations, missing aria-labels, and broken tab indices instantly.
  • Rule: If the a11y tab is red, you do not merge.


Accessibility Check with Storybook (https://storybook.js.org/)

2. The Distribution: NPM vs. GitHub Packages 📦

You’ve built the library. Now, how do your other projects consume it? You need a package registry. You have two main professional options for private code.

Option A: GitHub Packages (The Integrated Choice)

  • Cost: Free for public packages. Included in GitHub Free/Pro plans (with storage limits) for private packages.
  • Pros: It lives right next to your code. Seamless integration with GitHub Actions (CI/CD).
  • Cons: Authentication can be tricky. Developers need to generate a Personal Access Token (PAT) and configure a .npmrc file in their local environment to "install" the package.

Option B: NPM Private Registry (The Standard)

  • Cost: Paid. Requires a paid organization account (~$7/user/month) for private packages.
  • Pros: It is the “default” for the JavaScript world. Easier authentication (npm login). Zero friction for new comers.
  • Cons: It’s another monthly bill.

My Recommendation: Start with GitHub Packages if you are already in the GitHub ecosystem. It keeps your ops tight. If the auth friction annoys your team, upgrade to NPM.

3. The Versioning: Respecting SemVer 🏷️

This is the most critical part. When you change a component in your library, you cannot break the apps that use it. You must use Semantic Versioning (SemVer).

  • Major (1.0.0 → 2.0.0): Breaking changes. (e.g., Renaming onClick to onPress).
  • Minor (1.0.0 → 1.1.0): New features that are backward compatible. (e.g., Adding a new size="xl" prop).
  • Patch (1.0.0 → 1.0.1): Bug fixes only. (e.g., Fixing a z-index issue).


Semantic Versioning (Image is generated with Nano Banana)

4. The Architecture: “Barrel” Files and Tree Shaking 🌳

How you export matters. You want your consumers to be able to import exactly what they need, not the whole kitchen sink.

The Pattern: Use an index.ts (Barrel file) to control your public API.

TypeScript

// Good: Controlled exports
export { Button } from './components/Button';
export type { ButtonProps } from './components/Button';
// Internal helper functions stay hidden!
Enter fullscreen mode Exit fullscreen mode

Tree Shaking: Ensure your build tool (Vite, Rollup, or Tsup) is configured to support tree shaking. If App A only imports Button, it shouldn't be forced to download the DatePicker code too. This keeps your bundle sizes small.

Final Thoughts: The ROI 📈

Building a private UI library is an investment. It takes time to set up the repo, and the stories.

But the payoff is exponential.

  • Consistency: Every app looks like your brand.
  • Speed: Starting a new project? npm install @my-org/ui. You have a full UI kit in 30 seconds.
  • Maintenance: Fix a bug once, update the package, and it’s fixed everywhere.

Stop reinventing the wheel. Build your engine, then drive the car.

Ready to build your own? 🛠️

If you are interested in a full A-Z tutorial on how to build this comment below!


Top comments (0)