← blog

From screenshot to code: how AI-assisted capture-to-code workflows work

AI · July 30, 2026 · 11 min read

Why a model handed a PNG guesses, what it stops guessing when you hand it computed styles instead, and a realistic account of what these workflows do and do not produce today.

Paste a screenshot into a capable model, ask for the component, and you get something back that looks approximately right and is wrong in a consistent set of ways. Spacing that is close but not on a scale. A font that is the wrong one but the right genre. Colours that are the blended result rather than the layers. No hover state, because there was no hover state in the picture.

These are not model failures. The information was not in the input. This post is about what changes when it is.

What the model is doing with a picture

A vision model handed a screenshot is doing inference from pixels to intent. It is good at this: it will correctly identify that something is a card with a heading, a body, and a button, and produce plausible markup for that.

What it cannot do is recover values, because they are not recoverable. Consider a single surface colour. The picture says #1A1A1E. The truth might be white at 6% opacity over #0B0B0F, or a solid #1A1A1E, or a gradient sampled at the point you looked. All three render identically in that frame and behave completely differently in your app, over a different background, in light mode, or with a backdrop filter behind them.

So the model picks the most likely one, which is the solid colour, and you get a component that looks right in isolation and wrong the moment it sits somewhere else. Multiply that across type, spacing, radii, borders, shadows, and every state that was not in the frame.

What changes when the input is the element

A capture that carries the element’s real DOM and computed CSS removes the inference step for everything that was actually measured. The model is no longer guessing that the padding is about 24; it is told the computed padding is 24px. It is not guessing at the font; it has the family, the weight, the size, the line height, the letter spacing, and the fallback chain.

The practical effect is that the model’s effort moves from reconstruction to translation. Instead of "what is probably here", the question becomes "express these known values in this codebase’s idiom", which is a task models are dramatically better at and where mistakes are obvious rather than subtle.

Two things improve in particular. Structure gets right, because the real DOM tells the model that the thing is a grid with a span rather than three guesses that produce the same picture. And states survive, because computed styles for hover, focus, and disabled exist in the capture even though they were never in the frame.

The pipeline, concretely

Roughly five stages, whatever tool you use.

  • Capture. Read the live element from the page: markup, computed styles, the assets it references, and enough of the ancestry that inherited values are correct.
  • Normalize. Computed CSS is exhaustive and mostly noise. Every element has a value for every property. The useful subset is the properties that differ from the default and the ones that carry design intent.
  • Tokenize. Cluster the values. Sixteen slightly different greys usually want to be five. Spacing values almost always land on a scale once you look at the distribution. This is the step that turns a capture into something that reads like a design system rather than like a dump.
  • Generate. Emit code in the target idiom: React or plain HTML, Tailwind classes or CSS. This is the only step that really needs a model, and it needs it for judgment rather than for information.
  • Reconcile. Map the generated output onto what your codebase already has. This is the step that is almost always left to you.

The tokenize step is where the value is

It is worth dwelling on, because it is the step people skip and the reason a lot of generated code feels unusable.

Raw computed values give you a component with 43 hard-coded numbers in it. That compiles, renders correctly, and is worse than useless in a real codebase, because it encodes no intent and cannot be maintained.

Clustering first changes the output entirely. When you notice that the spacing values are 8, 16, 16, 24, 24, 24, 32, 48, you emit a scale and reference it. When you notice that four greys are within a small perceptual distance of each other, you emit one token. The generated component then looks like something a person wrote with a system in mind, which is the difference between code you paste and code you delete.

What these workflows are honestly bad at

A realistic account, because the category is over-promised.

  • Behaviour. Anything driven by state, data fetching, or logic is not in a capture and will not be inferred. You get the shell.
  • Semantics beyond the obvious. The capture knows it is a div with a click handler. It does not know it should be a button, or that this region wants a landmark, unless the source did it right.
  • Your architecture. Generated code does not know your component conventions, your styling approach, or what already exists. The reconcile step is real work and no tool does it for you today.
  • Responsive behaviour beyond the captured viewport, unless the capture explicitly gathered the breakpoints. One capture is one width.
  • Anything animated, unless motion values were extracted. Duration and easing are in the computed styles; the choreography of a multi-step sequence is not.

The correct expectation is a high-fidelity first draft of the static shell with real values, which is roughly the boring 60% of building a component from a reference. That is a large win and it is not the same as the demo where a screenshot becomes an app.

Structured context beats a screenshot in your own prompts

Even if you never use a codegen feature, this changes how you should prompt. Handing an agent a screenshot and a paragraph is the weakest available input. Handing it the extracted tokens and the structure is much stronger, and you can do that by hand today.

A prompt shaped like this outperforms an image by a wide margin:

Build this card in React with Tailwind.

Structure: article > figure(img) + div > h3 + p + button
Surface:   #0B0B0F, border 1px rgba(255,255,255,0.08), radius 16px
Type:      heading Inter 600 19px / 1.2, -0.01em
           body    Inter 400 13px / 1.6, #A1A1A1
Spacing:   card padding 24, gap 6 between heading and body, 20 above button
Button:    height 32, radius 10, bg rgba(255,255,255,0.10), 11px 500
Hover:     button bg rgba(255,255,255,0.16), 150ms ease-out

Every line there is a value the browser already knew. The only reason it is usually absent from a prompt is that the screenshot threw it away before the prompt was written.

How Stele does it

Stele captures elements from the live page with their real DOM and computed CSS, extracts design tokens (colours with assigned roles and a confidence score, type ramp, spacing, radii, shadows, gradients, breakpoints, motion), and generates React or HTML with Tailwind from that rather than from an image.

The same structured context is available as a prompt you can hand to your own agent, which is often the more useful output: you keep your architecture and your conventions, and the model stops guessing at values it was never given.

The reconcile step is still yours. That part is not a tooling problem yet.