In production · 2026
One interface system shared across every application I build
A private package with the primitives and tokens the portfolio, Rebote and future CRMs all consume, so they stop drifting apart.
- Role
- Design and development
- Primitives
- 29
- Design tokens
- 108
- Consuming applications
- 2
Problem
The interface components lived inside the portfolio. With a second application on the way, the only way to reuse them was to copy them — and two copies of the same button stop being the same button the moment someone fixes one of them.
This is not about looks. It is that every fix has to be made N times, and you have to remember all N.
Decision
A private, published, versioned package rather than pulling every application into one repository. The monorepo alternative avoids publishing and allows atomic changes, but it requires every project to live in the same repo, and not all of them can.
The rule that decides what goes in: if a component needs an app-specific prop to work, it is not ready to be shared. It stays where it is until a second project asks for the same thing. Without that rule, the library fills up with components carrying fifteen props to cover three cases.
Implementation
Twenty-nine primitives and a hundred and eight design tokens. The tokens are what really makes two products match: components can change, but if colour, radius and typography line up, two different applications still look like they came from the same place.
It ships as TypeScript source, uncompiled, and the consumer transpiles it. That way there are no two build steps to keep in sync, and no artefact that can fall behind the code.
The client-component directive goes only where it is needed: ten of the twenty-nine remain server components. Marking them all as client would have been quicker and would have pushed JavaScript into every page that uses them.
The registry read token reaches the containers as a build secret, not an argument: an argument is baked into the image layers and readable by anyone who pulls it.