Most designers building with AI are in one of two comfortable places, and I’ve lived in both.
The first is vibe coding from zero. The results can look great: cohesive screens, consistent colours, a real product taking shape. But open the code and the illusion breaks. Hardcoded values everywhere. Components that don’t exist, just similar-looking markup pasted wherever it was needed. It looks like a system. It’s a screenshot of one.
This is also why every vibe-coded product has that same familiar face. With no system of your own underneath, you’re shipping the model’s defaults, and everyone’s defaults are identical. The fashionable fix is to grab a design.md someone shared and drop it into the prompt. That swaps the paint. It doesn’t touch the problem, because a document nothing enforces is a preference, not a system, and the drift creeps back by the third screen.
The second comfortable place is building on a real system like Shadcn or Radix. They’re excellent, and shipping products with them is what they’re for. I used Meta’s internal system (XDS, now known as Astryx) for years and never once questioned how it was put together. That’s the thing though. Using a great system and understanding why it’s built the way it is are two different skills, and most of us (me included, until recently) only ever developed the first.
So I closed the gap the long way. I built my own system, robr0 DS, my own design.md, and my own guardrails, from a blank Figma file to a published package. Partly stubbornness, mostly history: I’ve spent years on design systems inside large enterprises, and every one of them got handed across a boundary to engineers where my rules became suggestions. Politely documented, quietly broken. I wanted to know, from the inside, what it takes to make rules that hold.
Turns out most of what I knew was right. What I didn’t have was any way to make it stick.
What it turned into
A learning project with no stakeholders and no deadline became two things in one repository.
The first is a component library, published to npm as @robr0/design-system. Dozens of components on a foundation of a couple hundred semantic tokens, still growing. I could have built on an existing library, and for shipping a product I probably should have. But the point was to learn what those libraries already know. Every value in mine is one I picked and can explain.
The second is a site at robertritacca.com, my portfolio twice over. There’s the classic sense: work case studies, and writing. Then there’s the sense I care about more: the construction of the site is itself the portfolio piece. How I structure a token layer, how I write a component API, how I record a decision I chose not to make. It’s all visible in the thing you’re using while you read about it. You can read me explain my standards, or you can go press on them.
The two halves aren’t just neighbours. The site installs the package the way a stranger would, so I’m my own first consumer and I feel every bad decision first. Nothing goes live unless every automated check passes. That plumbing was new to me, and it’s what makes everything below possible.
1. The tokens are the system
The one part I wouldn’t let an agent near, and the reason everything after it worked.
I hand-picked the palette, built my own colour ramp, wrote my own semantic names, and mapped every one myself in Figma variables, exported as JSON. That file is the backbone of every design decision in the system.
It runs in three tiers and nothing skips a tier. Primitive colours hold raw values and never get used directly. A semantic colour layer names intent instead of appearance, so a button asks for “the background of a primary action” rather than “teal.” Components only touch that middle layer. Since every colour token carries a light and a dark value, dark mode is one attribute flipping instead of a second set of components.
And colour is only one category. The foundation also carries the type ramp, spacing, radius, elevation, motion, icons, and logos, each with the same named-token treatment. The icons are Material Symbols locked to one weight and one style, so every glyph ships consistent. All of it is documented on the site’s foundations pages, and all of it is what components are built from. A component doesn’t invent anything. It assembles.
Here’s what that bought me:
I don’t design components in Figma anymore. I compose them in words.
I describe what a thing is and what it does, and the system applies my tokens as if I’d placed every value by hand. Which, in a sense, I did. The mapping was made by a designer up front, and everything downstream is that decision being applied over and over. A handful of components became dozens, each added the moment I needed it, without the foundation ever getting renegotiated.
The payoff runs live on the site: the playground page lets you override the primitives and watch the whole system re-theme. Change one raw value and it ripples through every button, card, and chart, because nothing anywhere references a colour directly. That one demo makes the case for semantic tokens better than this entire article.
The format is inspired by my years at Intuit working alongside their design system team, whose token foundations theme one system across TurboTax, QuickBooks, and the rest. I’d seen what that architecture buys at massive scale. Building it with my own hands was new, and it holds up just as well for one person.
If you’re starting from zero, do the semantic mapping yourself before you generate anything. Hardcoded values look identical to tokens right up until the first change. It isn’t quick, and for many designers it’s a big step. But it’s entirely a design skill, and everything else compounds on it.
2. Make your documentation load-bearing
Three documents sit at the root of my repo, and the AI reads them before it does anything. design.md owns how things look: tokens, colour rules, type scale, the rules for each component. content-design.md owns how sentences read: voice rules, banned words, and the AI patterns I kept catching in my own shipped copy. Above both sits claude.md, the operating manual: where facts live, what to verify, what to generate, what never to do. Two specs, and the layer that makes them count.
None of these are commentary. They are the instructions. When design.md says teal is only ever the action colour and never decorative, that sentence does real work, because it’s what the agent reads before it writes a line of CSS. When content-design.md bans a word, the word stops appearing. All three are published on the site verbatim and checked by the build, and an llms.txt lets any agent read the full spec straight off the public web. My documentation serves two audiences, human and machine, and the machine is the more demanding one.
Most design docs are written to be agreed with. These get written to be executed, and the difference shows up immediately. Vagueness that survives a review meeting produces garbage the second an agent acts on it.
The documents also have an executable arm: skills, written procedures the agent loads for recurring work. Scaffold a component with every registration step, audit a page, cut a release. A skill is a mistake you only have to make once, and whenever a check inside one proves mechanical, it graduates into the build itself, where nobody can skip it. Judgment hardens into procedure, procedure hardens into enforcement, and that ratchet only turns one way. Loops run on their own schedule, but all of them end at a local branch and a report. The agent isn’t accountable for anything it ships, so it takes the work to the last inch and the last inch is mine.
The tokens and components are the foundation; the documents tell the agent how to use it. Either alone is worth something. Together they compound: every piece the agent builds draws on the foundation exactly the way the documents say to.
3. The drift is the work
A written rule is only a guardrail if something holds it in place. This is that something.
Every design system I’ve worked on has the same disease. The Figma cover says one number, the docs site says another, the changelog says a third. We file it under hygiene and address it with reminders and quarterly audits, which means it never gets addressed. Drift isn’t a discipline problem. It’s the default whenever the same fact lives in more than one place.
So in my repo, every fact gets one home, and the home has a name: a registry. A small structured file holding the authoritative list for one collection: components, tokens, skills. Nothing else in the project gets to state those facts on its own authority. Every other surface is generated from the registry or checked against it at build time.
Take the component registry. One file lists every component with its name, category, and description. The navigation, sidebar, sitemap, page titles, card grid, and README count are all built from that file. Register a new component and every surface updates itself. Forget to register one, and the build fails and tells me which folder is missing. There is nowhere in the project I can type a component count by hand.
Here’s the mechanics when something ships. Every deploy starts with one command that fires a chain of validators before anything builds. One of them scans every line of component styling for raw values: a hex code, a pixel number, anything that should have come from a token. Find one, and the build fails right there. Nothing ships. There are exactly two ways past it: replace the value with the proper token, or write a structured comment directly above that line declaring the exception’s category and reason. No third option, no override flag, no “just this once.”
In plain terms, I can’t cut a corner without leaving a signed note at the scene. And because my agents follow the same procedures I do, work I delegate comes back with its exceptions already marked. The rule, the violation, and the reason live in the same three lines, and one search finds every exception in the system.
Here’s the lesson. In the real world, someone circumvents the system, because they disagree with it or don’t understand it, and you find out months later with no idea why. With these guardrails, circumventing fails the build, willing or not, and the only way through is to declare the override and the reason at the moment it happens. It’s a design leader’s dream: the system isn’t mandated in a deck. It’s mandated in the machinery.
Building it is how you learn it
So, back to the title. For most of my career I wasn’t building what I thought I was building either. I thought I was building systems. I was designing pictures of software and systems, and the difference is enforcement.
It’s also the way out of the sameness problem. Everyone’s vibe-coded product looks alike because the model is making the decisions. On my site, it can’t. I defined the foundations, so the palette is mine. I defined the components, so the patterns are mine. I defined the documents and the skills that enforce them, so every agent that touches the project builds to my standard without being reminded. The payoff: robr0 DS exists exactly as I instructed. Nothing on it is a default. View source, and what you find is the system I meant, not the one I got.
Sitting inside the build changed my definition of a design system. It isn’t just a library of Storybook components. It’s a set of rules with an enforcement mechanism attached, and the components were always the easy part. One rule nobody can break is worth more than fifty rules everybody agrees with. That’s the entire distance between a system and a suggestion.
Not everyone needs to build their own system. But the designer who lasts will understand what’s underneath the one they use, and nothing teaches that faster than building a small one yourself.
Mine is live if you want to poke at it:



