Paste a screenshot into a capable model, ask for the component, and what comes back looks roughly right and is wrong in a pretty consistent set of ways. Spacing that’s close but not quite on a scale. A font that’s wrong but at least the right genre. Colors that are the blended result instead of the actual layers underneath. No hover state, because there was no hover state in the picture to begin with.
None of that is really the model failing. The information simply wasn’t in the input. This post is about what changes once it is.
What the model is actually doing with a picture
A vision model handed a screenshot is doing inference from pixels to intent. And it’s genuinely good at that part. It’ll correctly spot that something is a card with a heading, a body, and a button, and it’ll produce plausible markup for it.
What it can’t do is recover values, because they simply aren’t recoverable from a flat image. Take a single surface color as an example. The picture says #1A1A1E. The truth underneath might be white at 6% opacity over #0B0B0F, or it might be a solid #1A1A1E, or it could be a gradient sampled at the exact point you happened to be looking. All three render identically in that one frame and behave completely differently the moment you drop them into your app, over a different background, in light mode, or behind a backdrop filter.
So the model picks the most likely one, which ends up being the solid color, and you get a component that looks right sitting alone and wrong the second it lands somewhere else. Multiply that across type, spacing, radii, borders, shadows, and every state that never made it into the frame in the first place.
What changes when the input is the element itself
A capture that carries the element’s real DOM and computed CSS removes the guessing step entirely for anything that was actually measured. The model isn’t estimating that the padding is somewhere around 24 anymore. It’s told the computed padding is 24px, flat out. It’s not guessing at the font either. It has the family, the weight, the size, the line height, the letter spacing, and the fallback chain, all handed to it directly.
The practical effect is that the model’s effort shifts from reconstruction to translation. Instead of "what’s 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 and easy to miss.
Two things improve in particular. Structure comes out right, because the real DOM tells the model it’s looking at a grid with a span rather than leaving it to guess between three approaches that all render the same picture. And states actually survive, because computed styles for hover, focus, and disabled exist right there in the capture even though none of them were ever visible in the frame.
The pipeline, concretely
Roughly five stages, whatever specific tool you’re using.
- Capture. Read the live element off the page: markup, computed styles, whatever assets it references, and enough of its ancestry that inherited values come out correct.
- Normalize. Computed CSS is exhaustive and mostly noise. Every single element has a value for every property. The useful part is just the properties that differ from the default and the ones that actually carry design intent.
- Tokenize. Cluster the values together. Sixteen slightly different greys usually want to be five. Spacing values almost always land on a real scale once you actually look at the distribution. This is the step that turns a raw capture into something that reads like a design system instead of a data dump.
- Generate. Emit code in the target idiom, React or plain HTML, Tailwind classes or plain CSS. This is really the only step that needs a model, and even then it needs it for judgment more than for information.
- Reconcile. Map the generated output onto what your codebase already has. This is the step that, today, is almost always left entirely to you.
The tokenize step is where the real value is
Worth dwelling on this one, because it’s the step people skip, and it’s the reason so much generated code ends up feeling unusable.
Raw computed values hand you a component with 43 hard-coded numbers scattered through it. It compiles, it renders correctly, and it’s worse than useless in a real codebase, because it encodes zero intent and there’s nothing there for anyone to maintain.
Clustering first changes the whole output. Notice that the spacing values are 8, 16, 16, 24, 24, 24, 32, 48, and you can emit a real scale and reference it directly. Notice that four greys sit within a small perceptual distance of each other, and you emit one token instead of four. The generated component then reads like something a person wrote with an actual system in mind, which is the difference between code you paste in and code you end up deleting a week later.
What these workflows are honestly bad at
A realistic account, since this category tends to get oversold.
- Behavior. Anything driven by state, data fetching, or logic simply isn’t in a capture, and won’t get inferred from one. You get the shell.
- Semantics beyond the obvious. The capture knows it’s a div with a click handler. It doesn’t know it should have been a button, or that this region deserves a landmark, unless the original source already got that right.
- Your architecture. Generated code has no idea about your component conventions, your styling approach, or what already exists in your codebase. The reconcile step is genuinely real work, and no tool does it for you today.
- Responsive behavior past the captured viewport, unless the capture explicitly went and gathered the breakpoints. One capture is one width.
- Anything animated, unless motion values were specifically extracted. Duration and easing live in the computed styles. The actual choreography of a multi-step sequence does not.
The fair expectation is a high fidelity first draft of the static shell with real values in it, which is roughly the boring 60% of building a component from a reference. That’s a genuinely large win, and it’s not the same thing as the demo where a screenshot turns into a finished app.
Structured context beats a screenshot in your own prompts too
Even if you never touch a codegen feature, this should still change how you prompt. Handing an agent a screenshot and a paragraph is about the weakest input you can give it. Handing it the extracted tokens and the structure directly is a lot stronger, and you can already 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-outEvery line in there is a value the browser already knew. The only reason it’s usually missing from a prompt is that the screenshot threw it away before anyone even got to write the prompt.
How Stele does it
Stele captures elements straight from the live page with their real DOM and computed CSS, extracts design tokens (colors with assigned roles and a confidence score, type ramp, spacing, radii, shadows, gradients, breakpoints, motion), and generates React or HTML with Tailwind from that instead of from an image.
That same structured context is also available as a prompt you can hand to your own agent, which is honestly often the more useful output. You keep your architecture and your own conventions, and the model stops guessing at values it was never given in the first place.
The reconcile step is still on you. That part isn’t a solved tooling problem yet.