Your design system has a source of truth problem. Neither Figma nor React is the answer—but specification-first design might be.
Your design system has a source of truth problem. Designers insist Figma is canonical. Engineers argue the React components are what actually ships. And when someone asks "which is correct?", the honest answer is usually "it depends on who you ask."
This was a tolerable tension when migrations were rare and frameworks lasted a decade. But we're now in an era where your Vue 2 components need to become Vue 3, your web components might need React Native equivalents, and that Figma update just broke your design token sync again. The question isn't whether you'll need to migrate - it's how painful it will be when you do.
I'm going to suggest something that might sound heretical to both camps: neither the code nor the design is the source of truth. The source of truth should be the specification itself.
The "code-first vs design-first" debate has always been a bit of a distraction. Code-first advocates point to runtime behaviour as the ultimate arbiter - what ships is what matters. Design-first advocates argue that intent and visual specification should drive everything - the design is the contract.
Both are wrong, or rather, both are arguing about implementation artefacts rather than the thing those artefacts are trying to represent.
When a designer creates a button component in Figma, they're encoding a specification: this is what it looks like, these are its states, here's how it responds to interaction. When an engineer implements that button in React, they're encoding the same specification in a different medium. The specification is the constant; the representations are variables.
The problem is that historically, we've never treated the specification as a first-class citizen. It lives implicitly in Figma files, in Storybook stories, in TypeScript interfaces, in documentation that's perpetually out of date. When these representations drift - and they always drift - there's no oracle to consult.
Imagine a world where you maintain a single, canonical specification for each component in your design system. Not documentation written for humans to read, but structured metadata that describes everything a component is and does in sufficient detail that code could be generated from it.
A calendar component might look something like this:
name: calendar
description: A calendar widget for displaying and selecting dates.
api:
props:
selectedDate:
type: string | null
format: ISO8601
description: Currently selected date
minDate:
type: string | null
format: ISO8601
description: Earliest selectable date
maxDate:
type: string | null
format: ISO8601
description: Latest selectable date
events:
onDateSelect:
payload: { date: string }
description: Fired when user selects a valid date
structure:
regions:
- name: header
contains: [monthLabel, navigation]
- name: dateGrid
layout: grid
columns: 7
behaviour:
interactions:
selectDate:
trigger: click on dateCell
guard: not disabled, within min/max range
effect:
- set selectedDate to clicked date
- emit onDateSelect
keyboard:
ArrowLeft: move focus to previous day
ArrowRight: move focus to next day
Enter: select focused date
This isn't documentation. It's a contract. It defines the component's API surface, its structural composition, its behavioural state machine, and its accessibility requirements. It's framework-agnostic because frameworks are an implementation detail.
I actually experimented with this approach a couple of years ago with a couple of Enterprise clients. It didn't get off the ground, partly because GPT-3.5 was awkward and partly because my code generator at the time couldn't do partial file changes - every generation ate up the full output context.
Three things have changed that make this approach genuinely viable now.
First, models have gotten dramatically better at understanding intent and producing idiomatic code. The gap between "translate this specification into React" and "produce code that a senior engineer would actually write" has narrowed considerably.
Second, agentic tooling now allows for iterative refinement. Instead of one-shot generation, you can have a system that generates code, runs static analysis, gets back specific failures, makes targeted edits, and loops until everything passes. This iteration loop was essentially impossible before.
Third, we now have robust tooling for static analysis and testing that can validate generated code against specifications. Does the generated component expose all the props in the API section? Does it emit all the events? Does it handle all the keyboard shortcuts? These become automated checks, not manual reviews.
If the specification is source, then code generation becomes a compilation step. You're not writing React components; you're compiling specifications into React components. The same specification compiles to Vue, to Web Components, to React Native, to Swift UI.
But unlike traditional compilation, this isn't purely mechanical translation. You need the generator to understand framework idioms - React's hooks patterns, Vue's composition API, Web Components' lifecycle callbacks. This is where platform guidelines come in:
platform: react
version: "18"
conventions:
componentPattern: functional
stateManagement: hooks
styling: css-modules
naming:
components: PascalCase
props: camelCase
events: onVerbNoun
The generator reads both the component spec and the platform guidelines. The specification defines what; the guidelines define how for a specific target. A single canonical specification produces multiple idiomatic outputs.
Here's where it gets interesting. If your behaviour specification is rigorous enough, you can generate tests directly from it:
behaviour:
interactions:
selectDate:
trigger: click on dateCell
guard: not disabled
effect:
- set selectedDate to clicked date
- emit onDateSelect
This becomes:
it('updates selectedDate when clicking a valid date', async () => {
const onDateSelect = vi.fn();
render(<Calendar onDateSelect={onDateSelect} />);
const dateCell = screen.getByRole('button', { name: /january 15/i });
await userEvent.click(dateCell);
expect(onDateSelect).toHaveBeenCalledWith({ date: '2025-01-15' });
});
it('does not select disabled dates', async () => {
const onDateSelect = vi.fn();
render(<Calendar disabledDates={['2025-01-15']} />);
await userEvent.click(screen.getByRole('button', { name: /january 15/i }));
expect(onDateSelect).not.toHaveBeenCalled();
});
The specification is the oracle. Generated tests verify that generated code matches spec. Human review confirms that the spec itself is correct. This inverts the traditional relationship where tests document emergent behaviour - instead, behaviour is specified upfront and tests enforce it.
If you buy into this model, several things follow that might not be immediately obvious.
Migrations become verification problems. Moving from React to Vue isn't a rewrite; it's generating a new target and running the validation suite. If the tests pass, the migration is complete. The specification hasn't changed, so the behaviour shouldn't either.
Multi-platform is just multiple build targets. The same specification compiles to web, iOS, and Android. Your design system genuinely becomes cross-platform, not through lowest-common-denominator abstraction but through parallel generation.
Figma integration inverts. With Figma MCP now available, Figma becomes a specification editor rather than the source itself. A designer modifies component states in Figma; those changes propagate to the specification; code regenerates. The visual tool remains familiar, but it's no longer pretending to be the source of truth.
Documentation writes itself. If the specification is rich enough to generate code, it's rich enough to generate docs. Storybook stories, API references, accessibility documentation - all derived from the same source. Bear in mind, documentation should still be mostly written by humans IF humans are consuming it.
I won't pretend this is simple. There are genuine challenges that need solving.
Escape hatches are essential. What happens when a platform needs something the specification can't express? iOS might need a specific gesture recogniser that has no web equivalent. You need platform-specific overrides that don't pollute the canonical spec. I'm thinking of this like CSS cascading - the spec defines the base, platforms can layer additions.
Spec evolution is versioning. How does the specification format itself evolve over time? If you change how behaviour is expressed, you need migration paths for existing specs. This is the Figma plugin API problem all over again.
Someone has to maintain it. If the spec is too abstract, designers won't engage with it. If it's too code-like, you've just moved the problem. The ergonomics of specification authoring matter enormously, and honestly this is the part I haven't fully solved.
Edge cases live somewhere. When behaviour is wrong, where do you fix it? In the spec? In generation rules? In framework-specific patches? There needs to be a clear hierarchy.
The real question isn't whether specification-first design systems are possible - the tooling has caught up and the approach is now viable. The question is whether organisations are ready to make the conceptual shift from "code is truth" to "specification is truth, code is output."
For most enterprises, the answer is probably "not yet, but soon." The pain of maintaining parallel implementations across frameworks isn't acute enough yet. But give it another generation of framework churn, another wave of platform expansion, another designer-developer drift crisis, and the current model starts to crack.
When it does, the organisations that have already separated specification from implementation will find migrations trivial while everyone else is still arguing about whether Figma or React is correct.
Interested in exploring specification-first approaches for your design system? We work with Fortune 500 organisations to future-proof their design operations. Get in touch.