A design system a whole team can build from
Two sources of truth, and neither matched what shipped. I made the build environment canonical, wrote eight rule families a component list cannot carry, and routed anything drawn from scratch to review.
Sole designer. Tokens, components, rules, templates, governance.
Engineers building UI, AI tools generating it, one designer reviewing it.
Three products and a marketing site. One brand, one codebase. A startup team: one designer, six people building against the system.
Claude Design as the canonical source; handed to engineering through Claude Code as a portable rule set.
title declarations doing one job, in a single product
tab families, four different bottom edges
components now, each with a contract and a spec card
rule families, plus a decision map and one traced screen
templates as sanctioned starting points
01 — Context
The platform serves private capital markets across three products and a marketing site: an AI toolkit for investment workflows, a network for industry groups, and a marketplace where allocators source and diligence investments.
I am the only designer. Everyone else who builds UI is an engineer, and most new UI arrives through AI tools. The system is read more often by a machine following instructions than by a person browsing a library, so it had to be an instruction set rather than a maintained file: precise enough that whoever builds next gets the same answer I would.
Illustrated with wireframes of the system's own structure rather than product screenshots. Names and values are as specified.
02 — The problem
Two problems. There were two sources of truth and neither one shipped, and a catalogue of components did not stop the drift it was meant to stop.
What the audit found
-
Ten title declarations for one job
Across a single product: ten separate header title declarations, four different bottom edges, five tab families. Each page was individually defensible.
-
Tokens that pointed at nothing
Two font weights with no font file behind them, so anything set to them was synthesised by the browser. Two shadow scales with near-identical names and different values. Two type scales where the same name meant 14px in one and 18.7px in the other.
-
AI-assisted building multiplies all of it
A plausible table, banner or title row can be drawn in seconds. It looks finished, so it reads as a decision, and a reviewer cannot tell an approximation from a decision without opening the markup.
Complete. Not what shipped.
Shipped. Incomplete and inconsistent.
Every value had two answers. Colour, variant name, hover state, spacing.
The tiebreak was one person. Asked the same question repeatedly, in chat.
So following the system was optional. Whoever was building either guessed or waited.
03 — Constraints
04 — Approach & key decisions
Four stages, in this order: audit what exists, settle where the truth lives, write the decisions down, build the route a new part takes to reach a designer.
Every header, token and near-duplicate component catalogued against what shipped.
One canonical source named, the repo demoted to structural reference.
The decisions between the components, written to pass or fail. Section 06.
A reporting route for anything drawn that the catalogue does not cover.
05 — What I built
Four layers and a register. Tokens fix the values, components fix the parts, rules fix the decisions between the parts, templates fix where a page starts.
Each layer exists because the one below it leaves a question open. The register runs alongside all four, for the case where nothing covers the answer. The drift was never in the values or the parts — it was in layer three, which most systems leave to a design review.
Leaves open: which token, where.
Leaves open: which one for this job, and how they sit together.
Surfaces · titles · geometry · spacing · elevation · states · motion · limits.
Runs beside all four, for the part no layer covers.
The decision map
Most drift was not invention. It was a reasonable component used for the wrong job: a filter drawn as a second row of navigation, a status pill used as a count, a menu hand-built from a button and a chevron. The map names the job, the component that does it, and the substitute people reach for instead. The substitute is always plausible, which is why it kept being chosen.
| The job | Use | Reached for instead |
|---|---|---|
| Move between a record's sub-pages | Underline tabs | The filled segmented switcher, which means a view and not a route |
| Narrow a list to this quarter | Toggle group in the toolbar | A second tab row: a filter drawn as navigation |
| Open a status or download menu | Menu button | Button plus chevron plus a hand-built surface |
| Show a count beside a nav row | The rail's built-in numeral | A badge, which in this system means a status |
| Lay cards out in columns | Card grid | A hand-rolled grid, which gives every card its own height |
The register
Every prototype eventually needs something the catalogue does not have. The register is the route that part takes to a designer: drawn and tagged, a row written with its picture, its spec and the components rejected with reasons, then handed over. Three kinds are judged by three tests. The same part drawn in two projects is one row with two sightings, which is what makes recurrence countable.
Tagged in the prototype. The build keeps going.
Picture, spec, and what was rejected and why.
One reviewer. A rejection has to name the replacement.
Rebuilt on tokens with a spec card. The hand-drawn markup is deleted.
Seen twice · not composable · tokens only.
A different meaning, not a different look.
Same arrangement twice · carries decisions · shorter than the markup it replaces.
06 — The rules
Eight rule families. Every answer is a number, a token or a named component.
The test for every rule: could an engineer who has never spoken to me apply it, and could a reviewer check it without opening the design file. “Use generous spacing” fails. “Twenty-four for sections, cards and every inset” passes. Writing to that standard made the rules usable by an AI tool and by a person for the same reason — neither can resolve a rule that rests on taste.
Every value was read out of the component source rather than restated from memory. A rules document that disagrees with the components is a third source of truth.
| Family | The rule |
|---|---|
| Surfaces | Page, then card, then content. Never a third fill. Floating layers are lighter than what sits under them, and one grey is hover only. |
| Titles | Size is set by the container, never by importance. One size per container type, listed. |
| Body and weight | One default for body, cells and field values. Four weights with a named job each, and two deleted because no font file backed them. |
| Geometry | Four legal control heights and six legal radii. Nothing else, including “close enough”. |
| Spacing | A short ladder, and one number for sections, cards and every inset, so page rhythm cannot be renegotiated per screen. |
| Elevation | No shadow by default. A shadow means the thing floats. It never signals importance. |
| States | Eight required states per component, each with one treatment. Every list owes an empty state and a loading state. |
| Motion | Durations by interaction type, two easings, and nothing animates layout. |
The self-check, and the override rule
The rules end in a check short enough to run: no colour literals anywhere; radii and control heights only from the legal sets; font sizes only from the scale; spacing only from the ladder; no raw table, button, input or select; shadows only on floating layers. It is mechanical on purpose — a rule that needs interpretation comes back as a question.
On overriding a component: layout properties are yours, and anything encoding what the element is — background, colour, border, radius, height, padding, type — is not. If no variant gives you what you need, that is a variant discussion. This is the rule that stopped the catalogue being forked at the call site.
A system written to pass or fail is also cheaper to hand a model than a component library to read. Closed gaps mean fewer invented parts, and fewer invented parts mean fewer screens generated twice.
Two lists published as gaps
The undecided list — page width, grid, breakpoints, layering, the success, warning and info colours, validation timing, table density — carries one instruction: ask, do not invent. The not built list names components that do not exist, from a mobile nav drawer to charts, with the same instruction: name the gap, do not ship a lookalike.
This was the least intuitive decision and it changed the most. An approximation looks finished, so it reads as a decision, and a reader cannot tell the two apart. Naming the gap keeps an unfinished system honest.
07 — Outcome
The system is in use and still growing. Three things are true of how the team works now.
Adoption, review time and how often the register is used are not measured yet. The counts above the fold are scope, not evidence.
08 — What this case doesn't cover
Let’s make a system your whole team can build from.
Tokens and components are the easy half. I write the decisions between them, and the governance that keeps a system honest while a whole team and its tools build on it.