From Figma to Vibe Coding

Over the past year, I’ve started shifting more of my design work out of Figma and into code. At first, it felt like an obvious upgrade. Instead of handing stakeholders a static mockup or a clickable prototype with a few carefully wired interactions, I could build something that behaved much more like the real product. Users could filter data, sort tables, open tooltips, switch views, and experience all the little states that are annoying—or sometimes impossible—to represent well in Figma.

And because I was using AI-assisted coding tools, I could move fast. A stakeholder could say, “What if this worked this way instead?” and I could often have a new version running shortly afterward. Ideas that might have been too expensive to prototype before were suddenly cheap enough to try. The result was better feedback, faster iteration, and something much closer to the experience we were actually trying to design.

For a while, it felt like I had found a much better way of working.

Then the prototype got big.

The prototype started behaving like a product

The project started small: some HTML, CSS, and JavaScript generated quickly to illustrate an idea. It wasn’t supposed to grow as big as it did. For what we needed at the time, it was perfect. The problem was that what started off as a single page, ballooned into nine tabs each with their own pop-ups, drilldowns, data tables and filters. As the project expanded in scope, we added more features and more “one tiny change” requests that were never actually tiny.

Eventually, the HTML and JavaScript files were thousands of lines long. That’s not just aesthetically unpleasant. When you’re coding with AI, the size and structure of the codebase actually affects how well the tools work. Prompts started eating up more context, changes became less reliable, and requests that had once taken one pass suddenly failed or needed several rounds of correction. Sometimes fixing one thing broke something completely unrelated.

I had somehow acquired technical debt in a design prototype.

Not exactly what I had in mind when I decided to stop making rectangles in Figma.

Once the prototype started growing, the lack of shared components turned small changes into scavenger hunts.

This problem was worse because we didn’t have an component library that could keep everything in sync. As the prototype grew, visually similar elements were created independently over and over again. With a few extra days I could’ve refactored the entire project. Unfortunately, my role wasn’t to build a beautiful codebase, it was to get design sign off under a tight deadline.

Every hour I spent cleaning up the underlying architecture was an hour I wasn’t spending solving the design problems people were actually looking at. Nobody in a review was going to say, “The information hierarchy isn’t quite right, but wow, great component structure.”

We had one pattern we called a “chart card,” basically a container with rounded corners that housed a chart and it’s accouterments like filters, legends, subtitles, as well as download and expand buttons. Conceptually, it was the same thing throughout the application. In the code, though, it might be “chart-container” in one place and “component-card-chart-large” somewhere else. Our quick-turnaround, just-to-see prototype had grown from one tab to nine, and different versions of the same UI pattern started multiplying across them.

Then someone would ask for a simple change to the card. In theory: five minutes. In reality: “Did I change this on every tab? Is this the same component? Why does this one have 12 pixels of padding and that one have 16? Where did domain-card-v2 come from?”

This is obviously the exact problem components and design systems exist to solve. In Figma, I would never intentionally make nine independent copies of the same component and then manually keep them in sync. But when you’re moving quickly with generative coding tools, duplication can creep in before you realize what’s happening.

A mature design system would have made this much easier. But even without one, I should have created a lightweight system at the beginning: a few tokens, a few shared components, and some basic rules about what gets reused.

The more realistic the prototype is, the more people think it’s real

The other lesson had nothing to do with code.

A Figma file has an obvious social contract: this is a design. Everyone understands that some things won’t work yet.

A functioning web application does not come with the same disclaimer.

Once stakeholders can click around, filter real-looking data, open menus, hover over charts, and move between nine different tabs, something changes. They stop interacting with it like a design document and start interacting with it like software.

Suddenly the questions become: Why doesn’t this button work? Can this save my settings? Can we connect this to the actual data? What happens on mobile?

Those aren’t unreasonable questions. The prototype looks like an application, so of course people treat it like one. But that can pull reviews away from the questions the prototype was actually built to answer: Is this the right workflow? Does this interaction make sense? Is this enough information? Are we ready for engineering to build it?

The irony is that the more successful you are at making a prototype feel real, the more carefully you have to explain that it isn’t.

Lessons learned: How to move from Figma to code prototyping

That first project taught me exactly how I wanted to build the next one. None of this has convinced me to go back to designing complex products entirely in Figma. Being able to put something genuinely interactive in front of people is incredibly useful, but there are best practices to make sure it speeds you up without slowing you down.

First off, it’s important for everyone on the project to agree on what a design prototype is and isn’t. The coded prototype is there so we can experience the design at a level of fidelity that makes meaningful feedback possible. It’s for testing ideas, getting sign-off, and giving developers something much richer than a collection of static screens to work from. It is not an early version of the production application.

Second, every prototype starts with a component library. This is non-negotiable. We didn’t have one, so I made one, component by component, and then taught my non-technical team just enough git and React to use it without feeling like they changed careers. We also created a shared prompt library and an AGENTS.md file that essentially tells the AI, “Before you invent another button, please check whether we already have a button.”

The result has been kind of great: we’re still moving incredibly fast, but now each prototype makes the next one easier. Instead of accumulating prototype debt, we’re accumulating infrastructure.

Next
Next

The 4 Stages of Building a Data Visualization Design System