Design systems

The gap between Figma and code

The gap between Figma and code

Modestas smiling in a couch.
Modestas smiling in a couch.

Modestas Sekstela

Design Systems

How it starts

A setup that looked complete

A couple of years ago, a platform redesign wrapped up looking like it had gone the right way.

First, the exploration phase: visual language locked in, light and dark mode worked through, screens redesigned. Then, the execution: the rest of the product redesigned and annotated inline, connector lines linking triggers to destination screens and overlays to clearly map navigation paths. Prototype flows for the interactions that needed motion. A Figma library with design tokens and a full set of reusable components, organised, documented, and ready to hand over. 

That felt like a natural end. It wasn't.


Good enough until it isn't

The library was handed to the client's design team.

From there, it moved to their engineering team. That transition happened without us. Their design and engineering teams were in different offices, different time zones, with infrequent sync. No Storybook. No shared component inventory. No named owner for the gap between design and code.

The tokens made it across. CSS variables were extracted from Figma and implemented correctly. That part worked. Everything beyond that came down to how closely developers chose to follow the specs, and without a feedback loop, close enough became good enough.

Seven or eight months later, when the redesigned UI started to be piloted with customers, discrepancies began to surface. Button variants had drifted. Icons slightly off. Stroke widths didn't match. Spacing was close, but not right. None of it would fail a functional test, but together it produced a product that felt quietly unfinished in a way users couldn't quite name. When complaint data came in, more than half of it had nothing to do with broken features. Things looked wrong, or felt wrong. Often both.

One example made the point clear. A page header the engineering team had made sticky, even though it was never designed to be, reduced the visible area for the content that mattered most on that page. Nothing was broken. But the experience was noticeably worse, and it came directly from an assumption neither side had thought to question out loud. That's the category of problem that never shows up in a functional test.

No library, however well built, can close the gap on its own once it leaves the hands of the team that created it.


The long way in

We heard about it. We pushed to be more involved on the code side. The idea landed well. The permission didn't.

It wasn't a budget issue, but a political one. Their development team had built the product from the ground up, and bringing in an outside studio felt like a challenge to their judgment, not a support to it. Nobody in management was willing to make that call.

Part of that was on us. The offer hadn't made the value clear enough.

What we were proposing wasn't a code review or a handover audit. It was taking the infrastructure work off their plate: primitive components, design tokens, Storybook documentation, accessibility semantics, versioning, breaking changes. The work that slows a product team down without ever feeling like progress. That pitch never landed the way it should have. So, we waited.

The client's own design team got there first. They were the ones living with the mismatch every day and the most tired of it. At some point we stopped waiting and scheduled a meeting directly with upper management. Visual comparisons, complaint patterns, and the design team's frustration laid out clearly. That was the moment things shifted.


The full picture

Once we had access, the picture was more complicated than expected.

The Figma library wasn't as intact as it had been at handover. Hundreds of screens were added by the internal team after they took ownership. Designers had been filling gaps on their own, building components locally when the original set didn't cover their needs. No documentation standards, no consistency checks, no connection to the existing components. It made sense in the moment, but it added up over time. The drift wasn't only happening in code.

If the Figma files had drifted, the code had drifted further. And unlike the design files, the code is what ends up in front of customers. The instinct was to tighten the handover: more redlines, stricter specs, clearer documentation. But that assumes Figma is the stable reference and code needs to catch up to it. But Figma wasn't fully stable. Anchoring the fix to something that was itself moving wasn't a fix at all.


Code as the source of truth

The decision: make code the source of truth.

Not because code is inherently better than Figma, and not as an argument for starting in code. If you're building a new product and the visual language is still being worked out, Figma is still the right place to explore. This wasn't that situation. The design was mature, the screens were signed off, and the failure was in the execution, not exploration.

Code is what gets built and what users interact with. When both Figma and code have drifted, and one has to anchor the other, it should be the one closest to the customer. Figma can inform and guide, but it can't define a reality it no longer reflects.

That doesn't mean design steps back. It means Figma stops pretending to define reality when reality lies elsewhere.


The underlying principle

The failure this project was recovering from is more common than most teams acknowledge. A well-built library. A competent handover. Good intentions throughout. The gap persisted because nothing connected the design to the implemented product, and nobody was explicitly responsible for monitoring it.

That's the part most teams skip. Not the library, not the handover, but the ongoing ownership of the space in between. Figma is a critical communication tool. In a mature product, it should describe reality, not define it.

That's how the gap starts. From now on, it's all about how we fixed it.


How we fixed it

Starting from what existed

Before writing a line of code, we audited what existed. The codebase, component usage, token structure, styling approach, and build setup. The existing Storybook told its own story: largely abandoned, with minimal coverage and no meaningful documentation.

A component API audit followed. The gap between the implemented components and the original Figma library was clear. Prop naming had drifted, variant coverage was inconsistent, and state handling had gone its own way.

The audit wasn't about assigning blame. It was about understanding what was fixed, what was flexible, and what would break if we touched it. The decisions that followed weren't arbitrary. They were the ones that fit.

One of those decisions was structural. Tokens and components live as separate packages. Tokens are plain CSS custom properties, with no framework or build pipeline tied to them. This means any team can consume them independently. Components assume tokens are present. Fixed versioning keeps both in sync, but the most important point is that it makes the design system the unit of release. Teams adopt DS v2, not tokens v1.4 and components v2.0.

 

What we built to close the gap

Tokens weren't designed from scratch. They were extracted from the existing global stylesheet and cleaned up. Redundant tokens were removed, low-usage ones were consolidated, a --ds- prefix was added across all custom properties for collision safety. The token set is fully visible in Storybook. Semantic colours, spacing, typography, radius, all in one place so designers and developers are never working from different references.

The CSS architecture question produced genuine disagreement within the team. Rather than letting seniority or habit decide, we built both implementations to the exact same specification. Same primitives, same public component API, same consumer contract, installed side by side in a benchmark app. The point wasn't to delay the decision. It was to make sure it was data-driven rather than political.

The Figma library was rebuilt from scratch alongside the component work, using the implemented component set as the reference. Structure, variants, and property names were mapped as closely as possible to the implemented API. Figma Variables mirrored the CSS token system, using the same semantic names on both sides, so designers and developers work from the same reference.

The goal was a 1:1 relationship between Figma properties and code props. Mostly it held. Where it didn't, the match was made at the level of intent rather than syntax.

In code, it's a component reference prop - you pass the icon component directly. The prop's presence controls the icon visibility. In Figma, the same concept is a boolean toggle (is there an icon here?) paired with an instance swap for which icon. Different representations, same question. Mirroring the API means matching the intent, not forcing two tools to look identical. Each expresses it in the language it understands.

Finally, Figma components were linked to Storybook via Code Connect. A designer working in Figma can navigate to the live Storybook story for any component in a single click. That link is what keeps drift from building up quietly.

 

Keeping the quality bar

If code is the source of truth, the documentation has to be good enough for a designer to use as a specification. That's a higher bar than most engineering teams set for Storybook.

In practice, that meant complete stories for every variant, size, and interactive state, not just the happy path. Controls that accurately reflect the real prop surface. Written MDX documentation per component covering intent and context, not just auto-generated prop tables.

Treating documentation as a deliverable goes against the instinct to ship code and fill in the details later. But without it, the Figma library has nothing reliable to mirror. The whole approach depends on Storybook being something a designer would open with confidence, not just check occasionally.

Before the first release, five audit criteria were defined and run across every component: API alignment, story alignment, story layout, MDX structure, and the interactive state contract. A component isn't done when it compiles. It's done when it passes all five. The bar is consistent and portable, not dependent on whoever happened to review it first. Over 200 tests passed across both implementations before release.

 

The part no tool can fix

The technical work was only half of it.

Shifting the source of truth from Figma to code is not a purely technical decision. It's a cultural one, and the design side tends to feel it most. It wasn't an argument about which tool is better. It was about which one reflected reality. And at that point, Figma didn't. At that point, the old library had drifted too far to trust it as a starting point. The only way back to a clean reference was to start from what had been implemented and build toward it.

That meant a new Figma library, built in parallel. Not a replacement that would break every existing design file overnight. The old library remained in place, still referenced by all the files that used it. The new one grew alongside it, component by component. Every component in the new library came with a guarantee: it was in Storybook, documented, and already in production.

That signal, if it's here, it's built, was the thing the old library could never provide.

Three things drove the decision to start fresh rather than correct in place:

  1. A clean set of Figma components mapping directly to the implemented component API, with Storybook as the reference.

  2. A clear separation between old and new so nobody was confused about which components carried the guarantee.

  3. The ability to migrate gradually without breaking links between existing design files and the library they were already using. 

Ownership was the last piece. A RACI was defined across token maintenance, component development, QA, and adoption. Each area had a named owner, and that mattered as much as anything else we built. Without someone accountable for maintaining it, the system would drift again. The conditions that caused the original problem don't go away on their own.

 

Where it landed

What exists now is the structure. A single component repository. A Storybook held to a documented quality bar. A Figma library built from it, with variables synced to CSS tokens. Code Connect wiring the two environments together, and a RACI with names on it.

The gap doesn't close by itself. It closes when someone builds the right structure and stays accountable for keeping it intact. That's the work most teams skip. It's also the work that makes everything else hold.

Modestas smiling in a couch.

modestas-sekstela.jpg

Modestas Sekstela

Design Systems

Modestas is the system architect who creates cohesion across the entire organization. He designs systems that scale with you and make it easy for you to deliver quality.

Newsletter

Sign up to our newsletter and be the first informed

By submitting the form, you agree to our privacy policy.

Newsletter

Sign up to our newsletter and be the first informed

By submitting the form, you agree to our privacy policy.

Newsletter

Sign up to our newsletter and be the first informed

By submitting the form, you agree to our privacy policy.

Newsletter

Sign up to our newsletter and be the first informed

By submitting the form, you agree to our privacy policy.