- A complete token architecture
- Components with variants and states
- Auto-layout throughout

A design system that catches its own drift
Mula's design system lived in two places, Figma and code, and they had drifted apart. I rebuilt it in Figma, built a pipeline to bring it into code, and built the governance that keeps the system true as more contributors, including an AI agent, build on it.
Role
Design Lead and Builder
Company
Mula
Timeline
February 2026 - present
Summary
Mula's design system was an incomplete, outdated Figma library that had drifted from the components running in the code. When the design source and the running code disagree, the cost adds up: changes get made twice, handoffs ship inconsistencies, and the same control is rebuilt several ways.
I rebuilt the system in Figma and built a pipeline to extract it into working code. Extraction alone does not keep a system true: as the code grew, my own decisions and an AI agent both pulled it off the system. So I built the governance that holds it, checks that fail the build when the system's rules are broken, with a single generative source as the next step.
01 / Context
Two systems that were meant to be one had drifted apart.
Rebuilding it properly started with the tokens: a set of primitives and a semantic naming layer, then the components built on them. When I took the plan to the front-end developer and asked what the code was already using, the gap was concrete.
In Figma
In code
- A few token variables, barely used and some duplicated under different names
- Screens full of one-off elements, the same button rebuilt many different ways
The plan was to close the gap: bring the rebuilt system into code and replace the one-off elements with the system's own components.
I am not an engineer, so I built a pipeline and directed an AI coding agent to write it. It ran in two stages. The first pulled the whole token system out of Figma, the primitives and the semantic layer over them, into a single source file and a live reference page.
The second ran each component through a four-step extraction, read its spec from Figma, build it against the tokens, document it, review it, one at a time so each was checked before the next.
02 / The Problem
Stated rules did not hold.
The rule was set from the start, in the prompt and in the project's written conventions: build only with the system's own tokens and components, never improvise styling outside it. As the project advanced, the agent drifted. I found that large parts of what it had built ignored the rule, styled off-system instead of assembled from it. Stated, acknowledged, and ignored. Nothing checked it, so nothing held it.
The drift came in two kinds.
- Agentic drift
- An AI agent going off the system, the same way any human contributor does, only faster and with more confidence.
- Design-code drift
- The same system living in two versions, Figma and code, that kept pulling apart. Holding them together meant making every change twice, by hand.
A stated rule is only an intention. For it to hold, breaking it has to cause a real failure, not just leave a note the agent can skip past.
03 / The Decision
Enforcing the rules, not just declaring them.
Declaring the rules had not been enough. They had to be enforced: checked by the system itself, automatically, instead of trusted to be followed.
The checks catch drift at three different moments.
The enforcement stack
01
AUTHOR TIMEStatic analysis
Reads the code as it is written and rejects forbidden patterns before it ever runs: a stray hex value, hardcoded spacing, any construction built outside the system. It does not compile.
ESLINT · DS RULES
02
TEST RUNBehavioural tests
Renders each component in a simulated browser and checks how it behaves, not how it looks, confirming it does what its contract says and running an automated accessibility audit over the result.
VITEST · VITEST-AXE
03
DEV RUNTIMEIn-component guard
Fires the moment a component is misused in development, when an icon-only button is given no label. The mistake surfaces where it is made.
DEV WARNING
Three checks, three moments: drift is caught where it happens, not where it's noticed.
No component ships without a complete accessibility contract: the document stating its role, its name, its states, and its keyboard behaviour.
Accessibility isn't one layer. Its visual side is built into the tokens (the palette's contrast, the focus ring, the field boundaries); its behavioural side into each component's contract, checked against it.
See it fire
The rule the agent kept breaking runs on every build: styling comes from the system's tokens, never a raw value. Hard-code a color into a component and the check names the off-system value and aborts. It cannot complete while the system is bypassed.
04 / The Judgment
The harder decision was what not to block.
Enforcing a rule is not all-or-nothing. For each one, the decision is how hard to enforce it: whether breaking it should stop the build outright or only raise a flag. Block too little and the warnings pile up until they are ignored; block too eagerly and the first time a check stops legitimate work, someone switches it off, and the whole layer of enforcement goes with it.
Block only what is unambiguous, where a false alarm is almost impossible. Everything else is a judgment call, flagged for a human to decide.
- Hard stop
- A raw color or a hardcoded measurement
- Styling improvised outside the system
- A component missing its accessibility contract
- Token contrast below the AA threshold
- Flag for review
- Judgment calls, not mechanical ones
- Use of color (WCAG 1.4.1), checked per component, never as a blanket gate
- The secondary button's faint border, compliant because its label identifies it
The build does not complete
A human in the loop decides
What the checks can't catch
A single token change can shift the look of many screens at once, and because that shift travels through layout, it lands on components that never reference the token, so no rule about tokens or code catches it. Catching it takes visual regression testing: comparing a picture of every screen against a saved version and flagging whatever changed.
I deferred building it until the team scales. With few contributors and a small surface, setting it up and maintaining it would cost more than it returns.
05 / The Next Move
A single source would prevent the second drift at its origin.
The next iteration of this architecture moves the system's source of truth upstream to generate code, design, and documentation simultaneously from a single origin.
Source
Single origin
Code
Figma library
Documentation
The current governance stops contributors, human and agentic, from drifting off the system but it cannot fix the dual-maintenance problem. Generation from a single declaration prevents design-to-code drift.
- Read-only outputs
- The source is the only surface anyone edits. Code, Figma, and documentation are generated from it and stay read-only, so none can be changed on its own. With nothing editable in isolation, the three cannot fall out of sync, and design-code drift becomes structurally impossible.
- A tool-neutral source
- The source is a plain declarative file, not tied to any framework. Changing stacks regenerates every surface from the same source instead of rebuilding the system. The format was chosen for that portability.
The first piece of this already exists. Each component now carries a machine-readable declaration of itself, its variants, its tokens, its rules, and the build checks that the declaration matches the code. It is the context an agent builds from, not a written convention it can set aside. Generating code, design, and documentation from that same declaration is the step beyond.
06 / Reflective Learning
Extraction was the easy part.
Pulling components from Figma into code is close to a solved problem, and becoming more so. The work that mattered was not generating the system but keeping it true once an agent, or anyone, began building on it.
A declared rule is not governance. Prevent drift at the source where the structure allows it; enforce at the boundary where authorship is human or agentic and intent cannot be trusted on its own.