This site uses one functional cookie to keep feature rollouts consistent for you. Nothing is set until you choose. See the privacy notice.
Dev notes
The shared design system already lived in three npm packages and a set of thin wrappers, but nobody could see it. This is the story of building a live, in-app gallery at /design-system that renders every primitive for real, lets you play with its props, and links out to the pages where it already ships.
A design system only pays off when people reach for it instead of hand-rolling another button. The write-up on extracting the packages explained the plumbing, but plumbing does not convince anyone. What convinces people is seeing the real component, poking at it, and noticing it already handles the accessibility they were about to reinvent.
So the goal was not a screenshot wall. Every card on the page mounts the actual published component from @paul-portfolio/react. If a primitive breaks, the showcase breaks — which is exactly the pressure that keeps a gallery honest.
That matters because the package is shared. The same primitives back this Next.js app, a sibling Angular app, and Ketsup, so a component can ship and get adopted in one place before it lands in another. A card for a primitive this app hasn’t wrapped yet says so honestly under Availability instead of inventing an in-app link.
The page is declarative. A single catalog.ts lists every primitive with its tagline, a usage note, its accessibility guarantees, and the real routes it appears on. The page just maps over that data, so adding a component is a data edit, not a layout rewrite.
Keeping it as plain data unlocked the check I cared about most: a test asserts the documented set is exactly the component set exported from @paul-portfolio/react (minus the one non-component export, the cx helper). Add a primitive to the package and forget to document it, or document one that no longer exists, and CI goes red. This is the check that caught the gallery falling behind: the package had grown a Ticker, a Card, a Switch and more, and anchoring the test to the package — not this app’s thin wrapper barrel — is what makes that drift fail loudly instead of going unnoticed.
The Button playground dogfoods the system to build itself: the controls are the design system’s own Select, Input, and FilterBar. Change the variant, size, loading, or disabled state and both the live button and a generated code snippet update together. The snippet omits any prop left at its default, so what you copy reads like real, minimal code rather than an exhaustive prop dump.
An InfoTip next to each name opens a rich, multi-line note on how and when to reach for the component. Using the real overlay here matters: it proves the system’s own popover escapes the card’s overflow, opens on keyboard focus, and dismisses on Escape.
An earlier version also wrapped every live preview in a Tooltip repeating the tagline. On the Tooltip and InfoTip cards — whose previews are themselves an overlay — that stacked a second popover on top of the first and read as a glitch, and everywhere else it just echoed text already sitting above the preview. So it came out. The tagline stays as plain text, the InfoTip carries the depth, and Tooltip is still demonstrated live by its own card. Less is the fix.
Accessibility is not a section tacked on the end — it is the argument. Each card spells out what its primitive guarantees (labelled controls, focus rings, focus traps, live regions), and the page ships a vitest-axe test that fails on any violation. The showcase respects prefers-reduced-motion through the same shared hook the rest of the app uses, and the whole thing is keyboard traversable end to end.
The most persuasive line on the page is the one you cannot see: tab through it and every control takes focus and shows it. That is the whole reason to adopt a system instead of shipping the tenth bespoke dropdown.
The catalog validates that links start with a route path, not that the route resolves — a future pass could cross-check against the app’s real route table the way the landing graph test already validates category anchors. And the playground only drives Button today; the same pattern would extend cleanly to a controls surface per primitive.
Update — August 10, 2026
A showcase has one failure mode that matters: drifting from the thing it documents. A gallery showing how a button looked two releases ago is worse than no gallery, because people trust it.
So the page renders components straight out of the published @paul-portfolio packages rather than reimplementing them for display. Adopting 0.5.0 was then a version bump plus documenting what it added, rather than a hunt for every place the showcase kept a private copy of a primitive. That is the whole argument for consuming your own published artefact instead of importing from source: the upgrade tells you what broke.