Docs read in a fixed order
The front door names the library and the order to read the rest in, so an agent can't guess halfway through. The components.json index is generated from the code, so it never goes stale.
I was LettrLabs' first and only Product Designer. There was no design system, so I built one from raw hex in Figma — then, after I was let go, took it all the way into a packaged React library an AI can build from. I had it audited against a written standard; it came back bad, and I published it anyway. That's the part worth reading.
# it's on npm
$ npm install @ivancreatelabs/design-system
# load the tokens once, at your app root
$ import "@ivancreatelabs/design-system/tokens.css"
# then build with it — Button, DataTable, MapCard…
$ import { Button, MapCard } from "@ivancreatelabs/design-system"The counts come from the repo's own scripts, not from me. This is a personal project — it isn't running in production anywhere.
A Figma library and a codebase drift apart from day one. I'd built the Figma half and written the rules, but never had to defend either against a compiler — so I wanted a straight answer: is the system actually consistent, or does it just look that way in Figma?
Rebuilt the library in React on three-tier tokens, wrote the rules in a format an AI can read, and packaged it with versions and releases. Then a three-stage agent pipeline graded 76 of 95 components against the written standard; 19 remain explicitly ungraded.
Blunt: not one component got an A, and the worst category was the rule I was most sure of. I fixed everything a script could catch and published the rest as open findings — rather than quietly closing them.
The installed library running live in your browser. Real product surfaces — sign-in, billing, a pipeline dashboard, an org-scope editor, a map, a mail-merge print preview and more — each built from the same components and tokens — the fields type, the toggles flip, the layout reflows.
Every surface is a live instance from the installed package — nothing here is a picture.
When I joined, LettrLabs had no design system at all. In Figma there was nothing — raw hex colours, no text or type styles, no components, and no designer had ever owned it. In the product it showed as 10 button styles, 4 table patterns, up to 5 "primary" buttons competing on a single screen, no documented UX process and no voice. On the screens that drove revenue, buttons fought for the same click.
So I built the system, and I built it with the people who'd live in it. I worked directly with the CTO, the CEO and the engineers to pin down the primary action on each screen, the core flows and the feel. From raw colours up, I built the Figma foundation — type styles, tokens, components — and held it to one rule: same job, same pattern. An orders table behaves like every other table. One primary action, one CTA. A mental model you learn once and reuse everywhere.
Here's the honest part: even as the sole designer, the system still drifted and regressed — we rebuilt the library and workspace twice in fourteen months. Rules that live in one person's head, even mine, don't hold — so they had to become machine-checkable. That's the bridge from the Figma work to the AI validators, and later to the lint-enforced package. After I was let go, I finished the job: I rewrote the whole system as a packaged React library with rules a script — or a model — can enforce. The rest of this page is that system.
"We spent a week arguing about one button. It sounds inefficient — but that decision became the criteria every component after it was measured against, and the rest went fast. Working with engineers who think in systems, my job wasn't to control the outcome — it was to find the decision they already wanted, and make it consistent."
The 439 tokens are layered so meaning drives the UI, and named the same way every time: category, role, intensity.
brand/bg/default, neutral/text-icon/strong — so what you meant stays the same even when the value under it changes.
Rendered straight from tokens.css — the swatches are the live values, not a diagram of them.
Forty of the ninety-five, each a real interactive instance from the installed package — flip the toggles, type the fields.
Worth saying plainly, because the count hides it: 56 of the 95 are global primitives built as a system (Button, InputField, DataTable); the other 39 are product one-offs — map tools, charts, org and campaign screens — I pulled into one place. Both are real components; only one set was ever built to a standard. The audit felt that difference.
Writing the docs for an AI made them better for people too — you can't tell a model to "use appropriate spacing," so every rule had to become something checkable, or it got cut. Four things carry the system to whoever, or whatever, builds next.
The front door names the library and the order to read the rest in, so an agent can't guess halfway through. The components.json index is generated from the code, so it never goes stale.
The core rule first: search for what already exists and reuse it before making anything new — and never hard-code a colour, size or radius where a token exists. Flag what's missing; don't paper over it.
Every message follows a pattern — what happened, what it means, what to do next. Give a model the state; it fills the blanks.
System failure → "We couldn't [action]. [Next step]. If it continues, [fallback]."Validation → "[Item] is required. [Action] to continue."Not every difference is a mistake, but each gets a decision — how a one-person system survives a growing team.
Violation — breaks a rule → fix the designDeviation — right value, no token → fix the bindingNew pattern — undocumented → name it, or replace itI couldn't audit 95 components by hand, and I didn't trust myself to. So I ran a three-stage agent audit—write the standard, grade 76 components against it, then fix—and let the written rules, not my taste, decide. Nineteen components remain explicitly ungraded.
Every row is a script that fails the build, so it can't quietly regress — numbers anyone with the code can reproduce.

The audit ships with the system, unfixed findings and all — what it actually looks like beats what its owner says. Two categories are closed and enforced by the build; the other eight are open or partly open, and I'm naming each rather than call the audit complete. Anything without a check behind it drifts back, whatever I claim today.
| Category | What the audit found | Status |
|---|---|---|
| semantic-token | Raw values where a token exists. The biggest category in the audit, and now the most strictly enforced — the lint fails the build. | Closed · enforced |
| ref | Refs not forwarded, or forwarded then dropped before they reach an element. Every component now gets rendered and checked. | Closed · enforced |
| a11y | Labels, error announcements and icon labelling are fixed and checked. Keyboard navigation, focus trapping and expanded/selected state aren't — several menus and overlays still can't be used properly from a keyboard. | Partly open |
| dynamic-data | Components shipping hardcoded sample data as a default, or missing their loading, empty and error states. The data table is fixed; the rest aren't. | Open |
| props-api | Names that don't match sibling components, missing passthrough, and props leaking onto DOM nodes. | Open |
| controlled | Components that handle controlled or uncontrolled use, but not both properly — a parent sets a value after mount and nothing happens. | Open |
| state · consistency | States you can't reach or can't tell apart, and components solving the same problem differently from their siblings. | Open |
| ssr | A few components touch window or document outside an effect, which crashes server-side rendering. | Open |
| no tiering | The manifest lists all 95 flat — nothing in the data tells an agent a map control isn't a reusable primitive, so it invites reuse of one-off product components everywhere. | Open |
| never audited | Nineteen components never got graded. They pass the automated checks, but nobody has actually read them yet — and I'd rather say that than call the audit complete. | Open |
This never shipped to customers, so I'm not going to claim a business result. What it is: a worked example of how I'd run a design system with engineers involved, and proof I can take one past the Figma file.
Most design-system work dies at the Figma file. Mine is an installed, versioned npm package — tokens plus 95 components — that a team could build on today. That's the half I'd never taken a system through before.
A Senior PM built three working prototypes straight from the same system, so it's not just consistent for its author — someone who didn't design it could build with it.
I ran an agent audit against a written standard and published what it returned—open findings and all—instead of the version that flatters me.
I can hand an engineer tokens and components that forward refs and pass a lint — not a Figma link and a conversation about what I meant.
The rules that survived the audit were the ones a script could check; the ones I'd written as good taste got broken everywhere, by me as much as any model. So the check comes first now, or the rule doesn't count.
Give an agent a readable spec and a reuse-first rule and it builds from what's there. Give it neither and it invents — a design problem before it's a tooling one.
The open list is the roadmap. Keyboard and focus first, because that's the one where a gap actually locks people out instead of just looking messy. Then the props API, so components stop surprising the people building with them.
The package is why any of this holds: one versioned source of truth a fast prototype can pull from and a team can build on — so a fix travels both ways instead of dying in a single screen, and a demo and production stop drifting apart. That's the part an HTML mockup can never be.
So the work now is narrow: turn more of the open list into checks, and drop any rule I can't enforce. A claim I can't hand someone as a command they run for themselves isn't a finished rule — it's a hope with good intentions.
"A design system earns its keep when the same rules produce the same answer — whether a designer or a model is doing the work."