MNEE UI — A Product-Led Design System
Shipped `@mnee-ui/ui` v0.1.1 — Figma library, npm package, docs site, and a Tailwind v4 dogfooding fork of the consumer, all owned by Product Design.

A design system as a product, not a Figma library
MNEE is a stablecoin product that ships its own merchant surfaces — a merchant portal, a Pay checkout, several embedded experiences. By early 2026 the surface area had grown faster than the supporting visual language. Different frontends were diverging on button styles, modal behaviors, table densities, and even the meaning of "primary." Engineering was making perfectly reasonable choices in isolation, but the result felt like three different products under one name.
The MNEE merchant portal — younger, web-only, smaller team — was the right place to prove out an end-to-end model: design library, code library, docs, and a real consumer, all owned by Product Design. This case study is about MNEE UI: the npm package @mnee-ui/ui, the Figma library that drives it, and the still-in-flight adoption story in the merchant portal.
Three needs, one invisible blocker
The merchant portal had a working design language only in Figma. Engineering reimplemented patterns per feature branch, and inconsistencies compounded with every shipped surface. We needed three things at once:
- A published code library so engineers stop rebuilding primitives.
- A Figma library that is genuinely the source of truth — not a stale mirror.
- A tight enough feedback loop that design changes propagate without political overhead.
The harder, less visible problem: the merchant portal was on Tailwind v3.4.18 and the engineering team didn't have bandwidth for a v4 migration. If I built the system on v4 it wouldn't drop in. If I built it on v3 it would be obsolete on arrival. I chose v4 anyway, and then I had to solve the adoption blocker myself.

The Figma library is the source of design truth; the npm package is the code implementation of that truth. When I change a token or component, the Figma file moves first, then the code, then a new patch release goes out. This is the reverse of what most engineer-led systems do — and it works because I sit in both halves of the loop.
Hard scope line. MNEE UI ships primitives and reusable composites — Button, Badge, Card, Input, Toast, Icons, Banner, Table, Drawer, Modal, Alert, CodeBlock. It does not ship anything Redux-bound or business-specific — no auth screens, no BalanceCard, no TransactionRow. Those live in product repos and consume the primitives. This rule alone has saved me a dozen judgment calls.
Deliberate tech bets the eng team hadn't ratified. Tailwind v4. Design tokens in app/globals.css under the v4 @theme {} block. cn() for class merging. Variants as Record<Variant, string> objects, never switch statements. lucide-react for icons. No CSS-in-JS, no styled-system, no runtime theming layer. The package's only peer dep is tailwindcss: ^4.

The part I'm proudest of: I solved the adoption blocker without asking engineering to drop anything. I cloned merchant-portal-frontend locally, branched off feat/refund, and migrated that branch to Tailwind v4.2.3 in a single commit (2cf8f93). That fork is now my dogfooding harness — every component I publish gets dropped into a real screen of the real portal before I cut a release. When engineering does eventually run the v3→v4 migration upstream, they'll have a working reference.
The docs site lives at the same URL surface as the system itself — mnee-ui.vercel.app. The docs aren't a separate Storybook bolted on later; they're the same Next.js 15 app that hosts the canonical examples. New component → new docs page → new release, in the same PR. components/ui/ is the published package; app/docs/ is the docs site. One repo, one source of truth, one release, no drift.
Key decisions
- Tailwind v4, even though the consumer is on v3. Building backward would have meant rebuilding the system in twelve months. I absorbed the adoption cost into a product-side dogfooding fork instead.
- Federico publishes, not CI. v0.1.1 is a manual
npm publishfrom my machine. Deliberate at current scale — release surface tight, cadence honest. CI comes when the consumer is live upstream. - Vanilla Tailwind v4, not ShadCN runtime. The Figma file is called "ShadCN" because the primitives borrow the aesthetic, but the code has no ShadCN dependency. Less abstraction, fewer breaking changes, simpler diffs.
- Hard exclusion list. Anything Redux-bound stays out:
auth/*,merchant/*,Payment/*,BalanceCard,ActionButtons,TransactionRow,TransactionDetailsModal. These belong to the product repo, not the design system. - Dual-purpose repo.
components/ui/is the published package;app/docs/is the docs site. One repo, one release, no drift. - Code Connect on the roadmap, not the critical path. I want the Figma file mapped to the npm components, but I'm not blocking shipping on it.
.figma.tsxstubs exist; coverage is incomplete and acknowledged.
What shipped
The system in use



Reflection — refusing to wait for capacity
The most useful thing I did was refuse to wait for engineering capacity. If I had built MNEE UI on Tailwind v3 to "match" the consumer, the system would already be on a deprecation path. Maintaining a product-side fork felt awkward at first — designers don't usually run migration branches — but it turned the adoption story from a political negotiation into a technical demo. When the eng team does pick up v4, the work is already proven.
The honest weakness: I'm the single point of failure for releases. v0.1.1 is fine; v0.5.x will need a real publish pipeline and at least one other person able to cut a release. The fix is mostly mechanical — a release-please workflow, a 2-of-N npm token policy — but I'd rather acknowledge the gap than pretend the bus factor is solved.