From the field #07 - Figma to code that doesn’t hallucinate
Part 1: the data layer is where it breaks
Most “design to code with AI” content is the same three steps. Screenshot the Figma frame. Paste it to the model. Pray.
It works. Right up until it doesn’t.
You get a component back. It looks right. The spacing is close, the colors are close, the layout reads correctly in the preview. You ship it. Two weeks later someone changes the header and the whole thing shifts four pixels, because the padding was never 16px. The model guessed 16px. It read a picture and returned its best guess of what the picture meant.
Here is the thing nobody says out loud about this workflow.
An LLM is a probabilistic engine. It guesses the most likely sequence of characters. 99% of the time it guesses flawlessly. The other 1% it produces a silent, expensive mistake - and the worst part is that it looks exactly like success. Green preview. Plausible CSS. A number that is wrong by four pixels and will detonate on the next layout change.
When you hand a model a screenshot, you are not giving it data. You are giving it pixels and asking it to interpret them. Interpretation is guessing. And the moment guessing enters your data layer, you have signed up for that 1% catastrophe on every single property the model couldn’t read precisely.
The fix is not a better prompt. It is not a smarter model. It is not waiting for the next release.
The fix is to never let the model guess in the first place.
Context before the model
This is the same principle I apply everywhere in AI-driven QA and automation. The model is the second step. The first step is a deterministic pipeline that hands the model clean, exact data - so there is nothing left to guess.
For design to code, that means one rule: the screenshot is never the source of truth.
Figma already knows the exact values. Every spacing token, every color, every radius, every font size lives in the file as structured data, not as a rendered image. The job is to read that structured data directly instead of asking a model to reverse-engineer it from a picture.
The Figma MCP server does this. get_design_context returns real CSS for a node - actual values, deterministic, the same on every run. get_variable_defs returns the design tokens. The screenshot endpoint exists too, and it has exactly one legitimate use: a visual reference for a human looking at the result. Never a source for a value. The moment a value comes from a screenshot, you are back in the 1%.
When the MCP path is not available, the REST API with a FIGMA_TOKEN returns the same node tree as JSON. Different transport, same discipline: read the structured values, do not interpret the render.
RULE ZERO
Deterministic extraction gets you most of the way. One more rule closes the gap, and it is the rule the whole approach stands on:
If a property is absent in the extracted design, its value is zero or none. Never estimate it from a screenshot. Absent means zero.
This sounds almost too simple to matter. It is the single most important line in the system.
Because the failure mode is not the value the design specifies. That part is easy - you read it, you map it, done. The failure mode is the value the design does not specify. That is where a model wants to be helpful. It sees a gap, it fills it with the most plausible number, and now you have a guessed margin that nobody asked for and nobody will notice until it breaks something.
RULE ZERO forbids the helpfulness. Absent in the design means absent in the output. Zero. Not “probably 8px”. Not “looks like a little spacing there”. Zero, unless the extracted data says otherwise. You enforce it as an invariant, not a hope: a declaration that has no value simply does not get emitted. The generator is physically unable to write a property the design didn’t define.
That is the difference between a tool you trust on a daily basis and a tool you have to double-check every time. A false 16px that looks right erodes trust faster than an honest gap that is obviously empty. Silent wrong is worse than loud incomplete.
One more guard
There is a second small discipline worth naming, because it bites everyone who builds this. When you read the implementation back to compare it against the design - getComputedStyle in the browser - you read it from the deepest text child, not from the container.
Read the container and you get inherited and default values that were never really set, and every one of them becomes a false positive. Read the leaf and you get what the element actually renders. Small detail. It is the difference between a report full of noise and a report you can act on.
One core, two ends
Here is where this goes, and it is the whole reason the approach is worth more than a single trick.
Once you have a deterministic extraction core - exact values out of Figma, RULE ZERO enforced, no guessing anywhere - that core does two jobs, not one.
You can generate with it: extracted values become a real component skeleton, CSS or a framework file, every property traceable back to the design, nothing invented.
You can verify with it: the same extracted values become the ground truth you check a built implementation against. Does the shipped padding match the design token, or did someone eyeball it? Measured, not eyeballed.
Same extraction. Same RULE ZERO. Two outputs. You build with it and you check with it, and both are honest for exactly the same reason: nothing in the pipeline ever guessed.
AI still does the dirty work here. It reads, it maps, it drafts. But it does the dirty work on deterministic data, and the engineer has the final say. That is not a limitation of the approach. That is the approach.
Next: the two tools that come out of that one core - the generator and the verifier - both public, both something you can clone and run today.
From the Field is what I actually build, what breaks, and what I learn. Real projects, real numbers, real bugs. No tutorials.