Why this exists
The problem in the current UpNext app, and what we're building instead.
The problem in the current UpNext app
If you open the production UpNext codebase right now and search for "button," you'll find at least four different button implementations. Three different shades of blue claim to be the "brand color." Different screens use different gray ramps that subtly disagree on warmth. When a designer asks an engineer to "match the new spec," what actually happens is:
- The engineer looks for the closest existing button
- Doesn't find one that matches
- Copy-pastes the closest one and edits the colors inline
- Ships it
- The next person does the same thing
This is how you end up with twelve buttons that should have been one. Inconsistency compounds — every new feature adds slight divergence because nobody can find the canonical version, and even if they could, modifying it feels scary (what else uses it?).
The cost isn't aesthetic, it's structural. Engineers spend time on visual debates that should already be decided. Designers spend time policing PRs for color drift. Both sides feel like the codebase is fighting them.
What we're building instead
A design system that makes consistency structural, not disciplinary.
That phrase carries the whole idea. The current state of UpNext relies on discipline — everyone needs to remember to use the right blue, the right gray, the right button variant. People will forget, because people always forget. The new system is structural: the way the code is set up makes the wrong choice difficult or impossible, regardless of who's writing the code or how careful they're being.
Three things together make it structural:
- One token system. There is exactly one
--primary, one neutral ramp, one set of status colors. Components reference token names, never hex values. If you want a different color, you change the token — and everywhere updates. - One wrapper per primitive. Every button in the UpNext app is the same wrapped
<Button>component. You can't accidentally make a different one because the lint rule blocks raw shadcn imports outside the wrapper layer. - One playground as the source of truth. The thing you're looking at right now — the Kitchen counter — IS the design system. When something changes here, it propagates to the real app via the migration playbook. There is no "design spec" file living in Figma that the code might be ignoring. The code IS the spec.
What success looks like
Six months from now:
- A designer asks for a new feature. The engineer assembles it from existing wrappers in the design library. No new "I made a one-off button for this" components.
- A product change requires the brand color to shift. The designer edits one token. Every screen in the app reflects it on the next deploy.
- A new engineer joins. Their first PR uses the wrappers correctly because the lint rule blocked them from doing otherwise — they didn't need to be told.
- An AI agent generates a new screen. It produces on-brand code because every prompt resolves to the wrapper layer.
That's what "structural, not disciplinary" actually delivers.
Why a playground first
Two reasons we're building the design system here in the Kitchen counter before touching the UpNext app:
- It's reversible. Mistakes in the token system are easy to fix when there's nothing depending on them yet. Once the real app uses them, every change has a blast radius.
- Designer review happens here. The playground is where the system gets polished — every state, every edge case, every variant — without being entangled with real product code. Sign-off happens here. Then the migration moves the polished system in.
The Kitchen counter isn't a temporary scaffold. It stays — as the canonical reference for the system, the demo environment for new components, and the place engineers point at when they need to confirm "is this how a button should look?"