/

A Private Component Registry for Your Team

Every team past five engineers has four buttons. Why a registry beats an internal package, and the three things that decide whether anyone uses it.

Serafim Korablev
Serafim Korablev
@korablev

Every team past about five engineers has the same problem: four buttons. Not because anyone decided to build four, but because the second person could not find the first one, the third found it and could not use it, and the fourth was in a hurry.

A private registry is the smallest thing that fixes that, and it is smaller than the npm package you were about to publish.

Why not a package

The instinct is an internal npm package: @acme/ui, versioned, imported everywhere. It works, and it brings three costs that kill it in practice.

Every change is a publish, a version bump and a dependency update across every app. Consumers cannot edit anything the props do not expose, so the first unusual requirement forks the component anyway. And the package is a build boundary, which means bundling, type generation and a release process somebody has to own.

A registry inverts all three. add copies the source in; the code is the consumer's; and there is nothing to publish or bundle.

The honest counterpoint: with a package, a fix reaches everyone. With a registry, it reaches whoever runs add again. Which of those you want is the real decision, and for a design system that is still moving, the registry usually wins.

What it is

The publishing guide has the format. The private part is only about who can fetch the item JSON.

Two mechanisms cover almost every setup. A token in the URL, substituted from an environment variable, which is the simplest thing that works in a terminal and in CI. Or a header, checked by whatever already authenticates your internal services. Either way the CLI itself needs no special support: it fetches a URL, and your host decides whether to answer.

The install then looks exactly like any other:

bash

Team libraries here

The hosted version of the same idea: a library on 21st that only your team can read, with the components published from Studio or the CLI and installed with a key that carries the team's scope. The libraries page is the public half of that surface; the private ones are visible only to their team.

The reason to use a hosted one rather than a folder on a static host is the part a static host cannot give you: a live preview per component, so the person deciding whether to reuse your button can look at it, and search, so they can find it at all. Discovery is the actual problem; the distribution was never the hard part.

Making it get used

A registry nobody knows about is four buttons with extra steps. Three things decide adoption.

One entry point. A single page or command that lists what exists. If finding the component takes longer than writing one, people will write one.

Your agent knows about it. This is the newest and possibly the strongest lever: point the coding agent at the team registry through MCP and it installs your button instead of inventing a fourth. An agent with no catalogue always invents.

A rule about what belongs in it. Registries rot when everything goes in. The workable line: a component earns a place when it has been needed twice, and it stays only while someone owns it.

What to put in it, in order

The primitives your product actually varies on: button, input, dialog, table row, empty state, and the two or three domain components every screen repeats. Not a copy of shadcn/ui, which is already a registry.

And the pieces that are not visual at all - the hooks, the formatters, the use-debounce everyone rewrites. The registry format takes hooks and lib files as first-class items, which is the part most teams do not realise they can use.

Where else to look

The honest list, because the answer is not always us:

SourceBest forTrade-off
An internal npm packageFixes that reach everyone, with real versionsPublish cycles, and consumers cannot edit
A self-hosted registry JSONFull control, no vendor, works with the standard CLINo previews, no search, no discovery
Team libraries on 21stPreviews, search and an agent that can read themHosted, and installs need a membership
A monorepo packageNo distribution at all inside one repositoryOnly works if everything lives in that repository

Taking one

Every component page has a live preview and the code. Installing goes through the shadcn CLI against our registry:

bash

That key comes from your 21st account, and installs require a membership. Set API_KEY_21ST once in your shell and the command works for anything in the catalogue.

Browse libraries →

Frequently asked

Registry or internal npm package for a design system?
A package makes every change a publish and a version bump across every app, and consumers can only customise what the props expose. A registry copies source in, so the code is theirs to edit and there is nothing to publish. The trade-off is real: with a package a fix reaches everyone, with a registry it reaches whoever runs add again.
How do you make a private registry authenticate?
A token in the URL substituted from an environment variable, or a header checked by whatever already authenticates your internal services. The CLI needs no special support, because it simply fetches a URL and your host decides whether to answer.
What makes a team registry actually get used?
One entry point that lists what exists, because if finding a component takes longer than writing one people will write one. An agent that can read it, so it installs your button rather than inventing a fourth. And a rule about what belongs: a component earns a place when it has been needed twice and stays while someone owns it.
What should go in a team registry?
The primitives your product varies on and the two or three domain components every screen repeats, not a copy of shadcn/ui. Also the non-visual items: hooks, formatters and the debounce everyone rewrites, since the registry format takes hooks and lib files as first-class items.

Published

Aug 20, 2026

Read time

5 min

Tags

How-toTeamsDesign systemsReact

Share