Somewhat ironically, or at least counterintuitively—translating a visual UI design from mockups in a tool like Figma into a buildable software interface is one of the most complex tasks in developing a piece of software, even though what it yields for users is the most concrete.
I say this is counterintuitive, because for most designers, looking at a UI in a visual design tool feels very concrete and intuitive, and the task of “making it work” feels like it should be a process of “making the interface look like that.” After all, if we’ve already done the work of making the UI look a certain way using one tool—especially if we’ve laboriously taken the time in that tool to structure our designs into components, styles, and variables—how hard can it be to translate from that tool to code?
I say this is ironic, because among developers, implementing a UI is often tossed off as a lesser skill than “hardcore” ones like algorithms, performance, and dev ops; and because software development firms have systematically devalued the role of UI implementation over the past decade by folding this work in with generalist “full-stack” developer jobs, and reducing pay scales for specialized roles in this area so that they may fall to someone more junior.
The result is that there are a lot of poorly-built user interfaces out there, far more bad than good, resulting in a lot of buggy user interfaces and sloppy UI code. Large Language Models, in turn, have been trained on large volumes of codebases with poor approaches to user interface code. There are efforts to train these tools to write better code by giving them access to take screenshots of rendered code so that an image-recognition model can compare the results to a reference—and this probably helps, but visual fidelity is not the only issue with poor UI code, and not the only challenge in building user interfaces.
The main reason that user interfaces represented in a visual design tool don’t translate easily to code is more like a system mismatch. Visual design tools allow us to visually describe the overall expected output of a program which renders a graphical user interface, but expressing interfaces in code requires an underlying systematic approach that is difficult to infer from visual output only.
In other words, building a user interface from a visual reference requires an act of interpretation, not just reproduction—a form of interpretation which most developers struggle with at best, and which LLMs can only extrapolate about.
So what does it look like to think about interface design from the engineer’s perspective? Instead of starting from a visual design reference and working backward to the code, start from the code and build up your way to an emergent interface design.
This is roughly the process I’d recommend:
- Start with a skeleton of basic, built-in UI elements to make something functional only.
- Shape your elements into an overall layout structure.
- Work your way in, laying out elements within layout regions, until you’ve got a rough layout.
- Encode an initial palette of values for fonts, colors, sizing, and spacing.
- Apply basic visual styles to elements, using as much shared code as possible—make all repeated elements look the same, and tie all of your styles to your palettes.
- Add in visual differentiation (variants) for elements that should contrast with each other for some reason, but otherwise behave the same. Extend your palettes as needed during this phase.
- Work on visual details to polish and finish your UI.
- If you get stuck at any point, accept the possibility of limitations (even unknown ones) and consider design choices that may be easier to implement.
As with any process, this approach is inevitably non-linear: polishing details may lead to rethinking the layout, for example. But the goal with this layered approach remains: to produce UI code that is more robust and easier to reason about and make fine-tuning adjustments to at the end.
Building the calculator example
Let’s look at an example of this by building a UI for a four-function calculator. We’ll walk through the same process step by step so you can see the engineer’s approach in action.
First: a basic skeleton
Step 1 is to start with a skeleton of basic, built-in UI elements—something functional only, with no attempt at visual polish yet. Here’s a calculator that does the math using nothing fancier than buttons and a display.
Shaping the layout
Next we shape those elements into an overall layout structure and work inward into the regions (steps 2 and 3). We’re not styling yet—just establishing where things sit.
A design palette
Before applying visual styles, we encode an initial palette of values for fonts, colors, sizing, and spacing (step 4). This gives us more control over the look and feel later, and keeps our styles tied to tokens instead of magic numbers.
Colors
Font
Sizing
Spacing
At this stage, it’s perfectly okay to start with a basic palette. The point is to build some control structures into the foundation of the design, for better adjustment later on. It’s almost impossible to imagine how a design palette will work until you start applying it to the design.
Applying the palette to the calculator
Step 5: we apply basic visual styles to the calculator, using as much shared code as possible and tying everything to our design palette. Repeated elements look the same; consistency comes from the system.
Icons as polish
For step 7—visual details and polish—we swap the operator characters for inline SVG icons. That way color and style still come from CSS instead of being baked into the glyphs, and we keep one source of truth for appearance.
(In general, it’s best to bring in an icon library if you need icons. In this case, we used the open source Lucide icon set.)
Visual differentiation
Step 6 is about adding visual differentiation (variants) for elements that should contrast—here, different groups of controls (numbers, operators, functions). We extend our palettes as needed so the system stays coherent and manageable.
At this stage, the benefits of the design palette and giving elements semantic names become more apparent, because design ideas like “I think the calculator body should use the darkest gray” or “The number buttons should be the lightest, followed by the main operators, etc.” now map more clearly to the code.
Refining the palette
Along the way to the latest design above, we also refined the design palette (tokens) to support the differentiated groups. That gives us something easier to work with and tweak later.
Base Colors
Semantic (usage)
Font
Sizing
Spacing
Radius
That’s the process in practice: we started from structure and system, not from a static mockup. Each step builds on the last, and the result is UI code that’s easier to reason about and adjust. When you build your own interfaces, you can use this same sequence—and when you get stuck, remember step 8: consider design choices that might be easier to implement.
Design tokens in practice
In the calculator example above, you saw how a design palette (encoded as CSS custom properties) gives you a layer of control between your design intentions and the raw code. This idea—named values that represent design decisions—is what the industry calls design tokens.
When you work with LLM coding tools, tokens become even more powerful. Instead of telling the LLM “change the hex value on line 47,” you can say things like:
- “Make the base palette warmer”
- “Increase the spacing scale by about 20%”
- “Switch the accent color to something in the blue-green range”
These are the kinds of design directions you’d give to a colleague. When the code is organized around named tokens, the LLM can find the right things to change—and the result is more predictable than asking it to hunt through scattered hardcoded values.
Naming things: shared vocabulary with the LLM
One more thing worth mentioning. When you’re working with an LLM on design changes, it helps to establish a shared vocabulary for the different parts of your interface. If you call something a “header bar” and the LLM calls it a “top navigation container,” you’ll talk past each other.
Try asking: “What would you call the different UI elements in this project?” The LLM will come back with names—maybe something like “control bar,” “score display,” “button group.” If those names make sense to you, start using them in your prompts. If they don’t, suggest better ones. The point is to get on the same page so your design directions are more precise.
This is a design skill, not just a coding skill. Naming things clearly—giving every element a role and identity—is how you build an interface that’s coherent and maintainable, whether you’re working in Figma, in code, or with an LLM collaborator.
Design tokens aren’t just visual, by the way. Interface strings—button labels, headings, error messages—can also be separated into a central reference. If you’ve ever worked with a content design system or internationalization (i18n), it’s the same idea: one source of truth for the words your interface uses.
If you’re building interfaces right now, try applying this layered process to your next project. Start from structure, not from a mockup. Build your way up to design, and let the system guide you.