In-System Design

A practical guide for designers: why my design system starts in code, and how I build, check and maintain the Figma library.

In-System Design: a three-part series.
Part 1: Building an AI-ready design system: Code → Figma
(you are here)
Part 2: AI prototyping inside your design system (3 November 2026)
Part 3: From code to Figma and back (10 November 2026)

For designers who know Figma well and have never written production code, and for the design system teams around them. Every technical word is explained in the box at the end.

For most of my career, the work went one way. I designed in Figma, a developer turned it into code, and when something did not survive the trip, we sorted it out between us.

Then I handed that same route to an AI, and what came back was unsettling, mainly because it looked fine. It read my Figma file, translated it faithfully, and filled in everything the file did not say with whatever seemed plausible.

The code that comes out is mediocre, and that is the part everyone frets about. Fair enough, since code quality is at least the part you can read, review and complain about in a pull request.

What worries me more is what gets invented: the focus order, the error states, what a screen reader announces, what happens when a request fails, or when a list comes back empty. None of that shows up in a screenshot, so nobody catches it in review.

So I turned the direction around. My design system now starts in code, and I generate my Figma library from it. Code always wins, and Figma is a mirror, not a second truth.

That sounds like a demotion for Figma, and in practice my Figma file got busier. This article is why, how I build the library, and how I keep an existing one honest, including the parts that still need babysitting.

Why doesn’t Figma-to-code with AI work well?

Because your Figma file was always supposed to be an abstraction. Let me explain: I sat down one afternoon and compared what a React component can express with what a Figma component can express, because that is the kind of thing I do for fun now, apparently. The life and soul of the party, I am. My count came out at roughly 17% coverage. For CSS, it was better, around 45%, and still nowhere near complete.

Those are my own numbers from my own comparison, not an official statistic. Check them against your own stack before quoting them in a meeting.

Everything Figma cannot say used to be filled in by a developer who could walk over and ask a question.

That gap is not a Figma failure, and no, I do not want Figma to close it. If focus order, container behaviour and ARIA labels lived in the file, you would need to be a front-end developer to use your own library.

A developer also notices what is missing. An AI does not. It takes the file literally, as a complete instruction, fills the gaps with whatever it has seen most often, and carries on, so the old direction now produces a component that looks finished and is partly invented.

Turn the arrow around and the same literal-mindedness becomes a gift. Generating from code, “it reads exactly what is there” is precisely what you want, because what is there is the real thing: real props, real values, real defaults, real states.

Flip it: Code as the source of truth, Figma follows

Code is the source of truth. Figma mirrors it. Where they differ, Figma is wrong.

I know how that lands in a room full of designers. Designers still do not work in code, and I have no plans to write production code myself. Figma does not matter less either. I think best on a canvas; I design there, and I would fight anyone who tried to take it away, so hear me out.

Hands-on: from a repo to a Figma library, step by step:

This is the workflow I currently use and demo, with the prompts I actually use. You don’t need to be a developer for this. You do need to be comfortable opening a repo and reading what’s in it, and the AI will walk you through the rest. So yes, some new territory, but stay with me! It’s great fun!

What you need:
1. A design system in code you can access, with Storybook running on it
2. An empty Figma file
3. MCP connection. I use and recommend Figma Console MCP by Southleft for this because it reads and writes variables properly, which the basic connection does not.

1. Get access to the code as a designer

A branch, a sandbox, or a playground repo. Ask development for the smallest safe thing: a copy where nothing you do can reach production.

If you want to practise first, download any sample design system as a zip and point your AI at the folder. I hand out a sample file for trainings and sessions, but you can also make your own. Connect that folder or branch with your AI. I will use Claude Code for my example. Your developers will most likely use the terminal or an editor; I use the Claude desktop app and the Claude Code tab.

2. Check that Storybook is there

Once you are connected, ask Claude Code to open Storybook for you.

What you get is a local website: it runs on your own machine, at an address like localhost:6001. Only you can see it, nothing is published, and it is built from the code sitting in front of you. It shows every component, live and clickable.

Storybook does not keep its own copy of your system; it lives inside it. Next to each component sits a small stories file listing its examples, so the page you click through is built from the real code and you can see, understand and compare it as a designer. That is also the key to prototyping with AI and generating sensible output, and I will come back to it in another article.

It is so great to finally go through the real stuff visually as a designer; I cannot believe I didn’t use this earlier! You can look at the Storybook of one of my samples here.

No Storybook? You can still do everything in this article. What you lose is the view and the component list, which I personally need to understand and compare, and for some parity checks later on.

If you are in a playground environment, you can just ask Claude to add it to your own copy and play there. In a real repo, it is a developer’s call, because it brings dependencies somebody has to maintain, so ask rather than install. It also needs to be Storybook 10 or newer, with the components manifest switched on, before an agent can read that list (this is for parity checks and prototyping later).

3. Open an empty Figma file

Clean, empty, and destined to become the library. Not your existing library, which comes later. Then open the Console MCP inside it.

From here, Claude Code is the hub between the two: one arm in the real code, one in the open Figma file. I can ask in plain words for something to be read on one side and built on the other.

4. Tokens and styles first, always

Components use tokens, so the tokens and text styles have to exist before the components do.

First find out where the tokens live, and yes, you can just ask Claude. In my repo they are JSON: one layer of raw values, one of semantic roles. In yours they might be CSS custom properties, or a Tailwind config, in which case I am sorry. Figma then splits them into two: colours, spacing and radii become variables, typography becomes text styles bound to variables. Console MCP does this well; you still have to check it, and you still need to know your way around Figma properly.

My prompt for this step, roughly as I say it:

I want an exact mirror of this repo in Figma. The MCP is on and the file is empty. Let’s do this step by step. Transfer the tokens first. Always use the same naming as the code, so Figma and the code match. Set up collections that match the code, primitives and semantic. Alias them. Scope them. Where needed use modes (e.g. light and dark mode). Do the same for the text styles, using the naming from code, and use variables as in the code. Anything you cannot express, write into GAPS.md always. Then stop and tell me if you need anything.

You will need to adapt this to your setup; mine is React with tokens in JSON, two layers. Once done, check in Figma that everything is set up correctly and compare with Storybook (I ask it to display all of this on a page for me).

5. Build two components as a test

Not the whole system. Two, ideally one simple and one awkward. I use Button and Alert, or some sort of dropdown; it’s fun with Figma.

Now build the Alert and the Button component as a test, set up as in code, using my variables and styles. Anything you cannot express, write into GAPS.md always (GAPS.md I explain below; it’s the best part!).

Then I go and look properly: are the variants complete, are the properties named as in code, is auto layout set up, are the tokens bound and scoped, did it carry the description across.

That last part is where the real value sits. The two sides do not only look alike, they carry the same names and the same properties, down to the spelling. No danger on one side and error on the other, no Card that is secretly a Panel. Everything that comes later in an AI workflow stands on that: prototyping with real components, checking for drift, sending a screen back to code.

If the two come out right, I run the whole library. And yes, that costs time and tokens. However, only once. Staying in sync afterwards is a different job: a contract, or in my case a parity check comparing two JSON lists. More on that soon.

6. Know what you need! Context travels with each element, like a little parcel

You might have heard that context is key when working with AI design systems, especially later as we want to prototype with all of this stuff. Most of what gets called context is a markdown file: a DESIGN.md, a rules file, a page of guidelines. But most of the context is baked into the things themselves. Every token, every component, every style: think of each one as a little parcel carrying its own context.

What is in the parcel:

  • the component name, identical on both sides
  • each property with its allowed values and its default
  • each token name, what it points at, and where it is allowed to be used at all
  • the description written once in code and carried into the Figma component
  • the code syntax on a Figma variable, so the variable knows what it is called in CSS
  • a link back to that component’s page in Storybook
  • and the gaps file, for everything that could not make the trip

None of that was typed into a document by hand, and to be honest, by hand I wouldn’t even get close. Markdown is for the why and the when, the rules and the exceptions. Everything factual gets generated and attached, and then it cannot quietly disagree with the code. Oh, how I love my MCP!

7. Ask for the gaps, every single time via GAPS.md

You might have noticed that I ask for GAPS.md every single time. I ended up with this one by accident. I wanted to know what had happened to all the things Figma could not express, so I asked the AI to list them, and the list turned out to be more useful than the components it had just built.

Write a GAPS.md: everything you could not express in Figma, everything you were unsure about, and anything I should check by hand.

The gaps exist whether or not anyone writes them down, and written down they become reviewable. My own gaps file contains things like line heights that are unitless in code and have to become pixels in Figma, states that only exist in behaviour, and layout rules Figma has no equivalent for. Each one with a note on what I decided. I also include a list of items the AI was unsure about that need my revision.

A gaps file turns the imperfect mirror into a list you can take to a developer and work through, and you might come up with more creative ideas on how to use it.

8. Turn what you learned into a skill

By the third run you will notice you are typing the same corrections. That is the moment to make a skill: a packaged set of instructions your AI loads automatically for this kind of job.

Mine creates tokens and components, writes the gaps file, expects Storybook, then screenshots each generated component and compares it against the matching Storybook story, and compares manifests (a check I use later for parity). It exists because of my own mistakes, and I update it most weeks.

One safety note, because I am asked this every time: a skill can run code on your machine. Make your own as a team or install them, but only from people you trust; read them, or have a developer look first or at least ask Claude to run through it before installing. Installing a random skill because it sounded handy is the same move as opening the “you have won a million” attachment.

Keeping code and Figma parity

Generating the library once is the easy part. Keeping it true as the code changes week after week is the open problem, in my setup and in the industry generally.

So I built myself a little parity checker and keep it as a status page inside Storybook; you can check it out here on my sync status page. You don’t need a page; you need a check, but I am a designer, so I need to see things visually.

How it works, in three steps, quite unspectacular:

  1. Storybook can automatically write a component list. If you are using the Storybook MCP, it generates a file (JSON) called a manifest: every component, its properties, the allowed values and the defaults, straight out of the code. I only found it because of prototyping, where that short list is what makes AI prototyping work at all, since the agent reads the list instead of your whole repo. Which, by the way, is what got me into this whole setup in the first place, as I was so frustrated with the code output of AI builders when prototyping.
  2. So I asked Figma for the same thing. With the MCP open, I had Claude write me a matching list of what is really in my library: same components, same properties, same values. You can see the Figma JSON file I use here.
  3. Then I ran the two against each other. Name against name, property against property, default against default. Everything that matches turns green, anything that moved turns red. Easy!

Both lists are JSON, which is just a text file where every value has a label, so a machine can compare them without interpreting anything.

That comparison is deterministic, and that is exactly what we want: same input, same answer, every time, with no AI in it. The AI only kicks it off in my setup. The downside of mine is that I still have to trigger it by hand, and a real team would have a more sophisticated setup that runs automatically on every change. But very few teams have that right now.

However, building it taught me more than reading about it ever did. I now have a much better idea of what my developers mean when they talk about checks, and I can sit in that conversation and help shape the workflow with design needs in mind.

Side note: Right now it only checks the architecture, the names, properties, allowed values, and defaults. Not the visual side, no CSS. I am working on that.
Another side note: I initially called this a contract, but it isn’t. A contract sits above both sides and both get built from it. Mine compares two sides, and code wins every time, so the honest name is a parity check.

“But I already have a Figma library. Can I update it with MCP? ”

Most teams have one, often with years of work in it. The same connection can compare instead of create.

I take a component I know is out of step, and I ask:

In my open Figma file there is an Alert. Check it against the Alert in the code. This is a library file, so instances depend on it. Think it through with me: would a change list next to it be best, or should you generate a reference version from code next to it so I can compare, or do you want to propose a direct fix? Tell me here in the chat and add this as a visual note in Figma next to the component.

What comes back is usually a report plus a clean reference component sitting next to the old one, which is a very calm way to have an argument with your own library (and yes, I renamed my component to Pizza just to show you a fail):

The whole check runs through the MCP, and the answer is generated straight onto the canvas, next to the component it is about.

Three warnings:

  • Work on a copy. Never let an agent loose in a live library that other people’s files depend on. Duplicate first, test the workflow, then decide as a team.
  • Deleted variants do not come back. Every variant has a key underneath its name. Delete it and rebuild it identically, and the instances out in the wild still will not reconnect. Rename and repair, do not delete and recreate.
  • It still needs babysitting. On a real system with real history, this is assisted work, not magic. Go component by component, because the AI is quick and tireless and has no idea which differences matter. Once you are done, you can put a deterministic contract or other parity check on top that just checks whether things stay synced.

I use this technique for any sort of check, e.g. accessibility

Once you are aligned, you can do so much! Here’s an example: accessibility checks run in code, and then I get a little note like the one above in my Figma file next to the area with the issue and suggested solutions to discuss. Love it!

This is what becomes possible once Figma and code hold the same tokens and structure: Contrast measured against the real tokens, then laid out in Figma through the MCP

What if your product has more than one source?

Everything above assumes one. My sample is one design system in code and Figma on top, so that code wins and there is nothing to argue about. With two it still holds: pick one as the source, mirror it into Figma, keep the names matching on the other side. At three- iOS, Android and web- it falls apart, because each of them holds part of the truth and none of them is wrong.

You will not be the one deciding how that gets solved, and it decides what your Figma file can be, so it is worth knowing which one you are living in.

A library per platform. You do exactly what this article describes, once per platform. Each library mirrors its own codebase, like three small projects side by side. The drift then is not really between Figma and code any more, it moves into the structure: there is no single Button, there are three, and they slowly grow apart. That is the kind of thing a team ends up talking about as a whole, and it is good to see it coming.

Shared tokens, separate components. The one I see most often. Colours and spacing come from one source and land everywhere, while each platform keeps its own components, which is correct rather than sloppy, since a sheet is not a modal. You can mirror that split in Figma: one library for the variables and text styles, shared by everyone, and a component library per platform on top of it. The tokens are then safe to rely on, the component names are not.

One platform as the reference. Usually web. Then it is this article again, one mirrored library, and