Skip to content

Design-to-code is easy to demo and hard to ship

Converting a design into markup takes an afternoon. Converting it into a codebase that compiles, is responsive, and a developer will accept is a different problem — and the gap between them is where most tools live.

AI engineering7 min readKriyaX Labs

In short

Generating markup from a design is straightforward; the hard parts are inferring real layout structure from absolute positions, producing responsive breakpoints rather than fixed dimensions, and proving the output compiles before anyone sees it. A pipeline that builds the generated project in a container and repairs its own failures is what separates a demo from something a developer will accept.

Why does design-to-code demo so well and ship so badly?

Because a screenshot hides the two properties that matter: structure and responsiveness.

A design tool knows where every element sits. Emitting elements at those coordinates produces something that looks pixel-identical to the design in a screenshot, which is exactly what a demo shows you.

What the screenshot cannot show is that the output is a pile of absolutely positioned boxes with fixed dimensions. Resize the window and it breaks. Ask a developer to add a row and they have to rewrite it. The visual fidelity that makes the demo persuasive is precisely what makes the code unusable.

So the interesting problem is not conversion. It is inference: recovering the structure the designer had in mind but never expressed.

What does layout inference actually have to work out?

Which elements are siblings in a row, which are stacked, and which spacing is intentional.

Get these right and you emit flex and grid containers a developer recognises. Get them wrong and you emit absolute coordinates with extra steps.

  • Grouping: elements sharing a vertical band with consistent gaps are almost certainly a row; the same on the other axis is a column.
  • Whether a gap is padding, a margin, or a deliberate space between unrelated groups — the pixel value is identical in all three cases.
  • Which dimensions are fixed by intent (an avatar) and which happen to be fixed because the designer drew one instance (a card).
  • Where content will grow. A name field that fits in the mock will not fit every real name, and the generated layout has to survive that.
  • Whether a repeated visual pattern is three separate elements or one component rendered three times.

How do you know the generated code is correct?

You build it. There is no substitute and no shortcut.

A model can produce code that reads convincingly and does not compile. It can import a component that does not exist, use an API that changed two versions ago, or emit a type error three files away from where it looks wrong. None of this is visible by inspection at any useful speed.

The only reliable check is to compile the project in a clean environment. That means an ephemeral container, a real install, a real build, and treating a failed build as a rejected output rather than a warning.

This is the same principle as verifying a generated exam question by solving it: do not ask whether the output looks right, execute the thing that decides whether it is right.

What should happen when the build fails?

Deterministic fixes first, then a model, then retry — and in that order for a reason.

Deterministic repair
A large share of failures are mechanical: a missing import, an unescaped character, a package absent from the manifest. These have exact fixes and should never be sent to a model, which is slower, costlier and less reliable at them.
Model repair
For everything else, hand the model the error and the relevant file rather than the whole project. Compiler output is unusually good context because it names the file, the line and the expectation.
Bounded retry
Cap the loop. A pipeline that retries indefinitely will eventually spend a great deal of money converging on nothing.

Why split it across several agents?

Because each stage has a different failure mode, and a single prompt hides which one occurred.

Separating layout analysis, component architecture, code generation and responsive adaptation into distinct stages means each produces an inspectable artefact. When something is wrong you can see whether the layout was misread, the component boundaries were drawn badly, or the code generation slipped — rather than staring at a wrong result with no idea which step caused it.

It also lets you spend differently per stage. Layout inference benefits from a stronger model; boilerplate emission does not. Collapsing everything into one call means paying the highest rate for the cheapest work.

Where should the output go?

Into the client's own repository, on the first commit.

A generated project that lives inside the vendor's platform is a hostage. The moment it lands in the client's own GitHub or GitLab, it is ordinary code: reviewable in a pull request, diffable against the next generation, and editable by hand without asking permission.

This also imposes a useful discipline on the generator. Code that a developer will read in a pull request has to be code a developer would have been willing to write, and that constraint improves the output more than any amount of prompt tuning.

What is realistic to expect?

A strong first draft that compiles, not a finished product.

The honest framing is that this removes the least interesting part of the work — translating a settled visual design into structure, styles and breakpoints — and does not remove product judgement, state management, or anything involving a real backend.

Treated that way it is genuinely valuable. Sold as replacing a front-end team, it will disappoint, and the disappointment will be deserved.

Questions this answers

Can AI convert a design into production code?

It can produce a strong first draft that compiles, provided the pipeline infers real layout structure rather than emitting absolute positions, generates responsive breakpoints, and verifies the result by actually building it. It does not replace product judgement, state management or backend work.

Why is design-to-code output usually unusable?

Because it reproduces the design as absolutely positioned elements with fixed dimensions. That looks pixel-perfect in a screenshot and breaks the moment the window resizes or a developer needs to change it. The hard part is inferring the flex and grid structure the designer implied but never expressed.

How do you verify AI-generated code?

Build it in a clean, ephemeral container and treat a failed build as a rejected output rather than a warning. Repair failures deterministically where the fix is mechanical, use a model only for the rest, and cap the retry loop so it cannot spend without limit.

Working on something like this?

Thirty minutes, no obligation.