---
title: "ADR 0002. Distribute as a shadcn registry"
description: "Ship components as copyable source through a shadcn-spec registry, and do not build a CLI first."
url: "https://opsinjs.pensievelabs.org/project/decisions/0002-shadcn-registry-distribution"
source: "https://opsinjs.pensievelabs.org/project/decisions/0002-shadcn-registry-distribution.md"
section: "Project"
kind: "project"
reviewed: "2026-09-02"
reviewer: "engineering"
aliases: ["why not npm", "registry distribution", "copy paste components"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="project" />

## Status [#status]

**Accepted.** 2026-09-02.

## Context [#context]

A component library can be delivered three ways: as an npm package you import, as
source you copy into your own repository, or as both. The choice is usually made
on developer convenience. In this domain there are two stronger arguments.

**Health products modify components.** They do so always, not occasionally. The
thresholds a range renders, the wording of a disclaimer, the exact phrasing of a
status sentence: these are the parts a clinical safety review changes, and they
are different for every product because the clinical context is different. A library
that makes modification mean forking has misunderstood the job.

**Copied source is auditable.** A file in your repository, in your pull request,
with your reviewer's name on it, is something you can put in a design-history
file. A version range in `package.json` resolving to a tarball is harder to
evidence, and the team that has to evidence it is the one buying this system.

The shadcn registry specification already describes exactly this distribution
model, and `npx shadcn add` already implements the client. Both are public,
stable and widely used.

## Decision [#decision]

**Publish a shadcn-spec registry under `/r` on the canonical domain, register the
`@opsinjs` namespace, and write no CLI of our own.** Installation is
`npx shadcn add @opsinjs/<name>`. The registry catalogue at `/r/registry.json` is
also what makes the shadcn MCP server work against this project without any
additional server.

## Consequences [#consequences]

* **Consumers own the code.** Upgrades are diffs, not version bumps, and nothing
  in a copied file records where it came from. Commit the files `shadcn add`
  writes, in their own commit, so a later `--diff` has something to compare
  against. [Upgrades and diffs](../../registry/upgrades-and-diffs.mdx) is the
  full procedure.
* **Fixes do not propagate.** A security or safety fix reaches only the people who
  come back for it. [Security](../security.mdx) advisories are therefore written
  for someone patching their own copy by hand, and that is a permanent cost of
  this decision rather than an oversight.
* **The MCP server works for free**, because it consumes the same registry
  catalogue. So does `/r/index.json`, which carries status, category and aliases
  for agents that want the answer without the files.
* **Bundle size is a per-consumer question**, not ours. The `<BundleSize>` block
  on a component page reports what the copied source costs, including whether it
  forces a client boundary. That is a normal integration question that almost no
  design system answers.
* **Not everything fits this model.** The colour engine is a callable API with
  real algorithms in it. It derives a ramp and computes APCA, and copying that
  into a product is worse than importing it. Those ship as `@opsinjs/*`
  packages, and the [Packages](../../packages/index.mdx) section documents them
  as an API. The registry is for components; the packages are for functions.

## Alternatives considered [#alternatives-considered]

**An npm package of components.** Rejected: modification means forking, and
forking loses the upgrade path entirely. It is also the wrong shape for a system
whose most-changed parts are strings a clinician has to approve.

**Both, from day one.** Rejected as premature. No component exists yet, and two
distribution paths mean two sets of documentation, two upgrade stories and twice
the surface. The packages arrive when there is something in them worth
importing.

**Our own CLI, first.** Rejected as building the shop before the stock. `npx
shadcn` already does dependency resolution, path rewriting, diffing and dry runs.
An `opsinjs` CLI is only justified by a job shadcn cannot do, and
[CLI reference](../../registry/cli-reference.mdx) says plainly which jobs those
are. Theme validation is the likely first one.

## Revisiting this [#revisiting-this]

Revisit when a component's logic is complex enough that a consumer copying it
cannot reasonably maintain it, or when the upstream registry specification
changes in a way we would have to fork rather than follow.

<LastUpdated />

<Reviewed />
