writing / 01
Contact
Writing · Essay 01 · A field report

Designing at the Seam

Everyone is debating where design's source of truth should live. I ran two experiments on my own design system to find out what alignment actually means. It opens with a bug I found in my own file.

PAOLO CONTI  ·  AUGUST 2026  ·  13 MIN READ  ·  COMPANION TO THE SEAM CASE STUDY

There was a bug in my design system for months, and I would never have found it by looking.

My button component has 100 variants, and each variant sets padding on four sides. That's 400 places where padding is connected to a token. During an audit I ran for this essay, I found that 280 of them were connected to the wrong tokens entirely: corner radius tokens. The buttons looked perfect, because the radius token and the spacing token happen to hold the same number, 8 pixels here, 16 there. The values matched. The connections were wrong. If I had ever rounded the system's corners by bumping a radius token, the padding of every button in the product would have quietly changed with it.

The detail that stings: 120 of those connections, in the same component, were correct. So this wasn't a convention or a misunderstanding. It was drift. My own hands, in a file only I touch.

And here's the part worth the price of admission. I fixed all 280 connections, and not one pixel on screen changed. That is exactly why the bug was invisible, and it's the thesis of this essay.

The drift that actually hurts is a failure of connection, not appearance.

Every process we run to keep design and code aligned, redlines, spec reviews, design QA, inspects appearance. That's why those processes never stopped the drift.

01

The debate we're actually having

There's a live debate in design right now about where the source of truth should live. One camp says Figma, because visual exploration is what code generation is worst at. One camp says the code, because it's the only artifact users ever touch. A third says neither: put a shared token file in the middle and generate both sides from it.

I came to this debate through twenty years of watching the design-to-code handoff fail in every organization I've worked in, and then through running the code-side answer on a real product. This isn't a position paper. It's a field report: what holds, what's thinner than advertised, and where the edges are.

02

The drift that never stops

Every design team I've been part of has run some process for keeping design and production aligned: redlines, spec pages, token libraries, Storybook, design QA the week before release. The tooling has improved dramatically over those twenty years, and the drift has never stopped. At some point I quit asking how to manage it and started asking why it exists. The answer is uncomfortable: design and engineering produce two separate artifacts, and both of them claim to be the product. There is a Figma file that says "this is the button," and a codebase that says "no, this is the button." Everything we call handoff is an ongoing negotiation between those two claims.

Every tool in the handoff category is a translation layer between those artifacts, and translation layers decay, because every change has to be carried across by hand and some changes always miss the boat. The drift isn't a clarity problem or a communication problem. It's structural. You cannot document your way out of a structural problem.

03

Moving the seam

So I inverted the usual direction: instead of asking engineering to meet design on a design-centric platform, design starts from the foundation engineers already use. Engineers are not coming to our platform. They have a codebase, dependencies, version control, continuous integration running automated checks on every change, and they're right not to leave it: the code is where the product actually lives. Every design-led attempt to close the gap asks engineers to orbit design's source of truth, and it has never worked.

Shadcn/ui is an open-source component library that already sits inside thousands of production codebases, explicitly designed as a starting point rather than a destination: your team owns the code, restyles it, extends it. I grounded my design system there, and I've been running the experiment on a pre-launch personal-finance product where I lead design. When I design a button now, it isn't a Figma interpretation of a button that an engineer will later re-derive in code. It's the same component, expressed twice from one structure.

04

What "one source of truth" actually means

"One source of truth" is where every pitch in this space overclaims, so let me be precise about what I actually have. Three tiers. The component architecture is genuinely shared: what a button is, what states it has, how variants compose. One definition, in the code, that both sides work from. The tokens are aligned by a thin mapping: colors, spacing, and type live as variables in code, mirrored as variables in Figma. Small, flat, maintainable, but a mirror, not a single object. And the Figma library itself is a projection. When a component changes in code, something still has to update its Figma counterpart; since Figma shipped canvas write access for AI agents this spring, that's increasingly an agent doing it on request. Which raises the question my opening bug already half-answered: a projection is only as good as its connections, and connections are invisible. So I tested it.

05

Experiment one: right value, missing connection

I had Claude, connected to my design system file, build a new piece of UI on the canvas. The instruction, in full:

"Make me a settings card for notification preferences: a title, two preference rows with helper text, and a save button at the bottom."

The instruction, exactly as given · no mention of tokens

A note on fairness before the results: the write-back tool does whatever the agent tells it to do, so these results describe the agent's default choices, not a limitation built into Figma. And the deck was stacked in the agent's favor: before the first run, it had already read my entire token system. Every collection, every name, every value.

The card came back looking exactly right. Correct text styles, applied without being asked. My real button component, placed as an instance rather than redrawn. Then I checked the connections. Of the 36 properties on that card that could have been connected to a token, the number actually connected was zero.

And not because it invented strange values. That's what makes the failure interesting. The border color was the exact value of my neutral border token. The padding was 24, exactly my spacing token. The corner radius was 12, exactly my radius token. Five of the seven values it hardcoded were numerically identical to a token in the file it had just read. The card would pass any visual review you could stage, and it is connected to nothing. Switch my file to dark mode and this card doesn't move. It also used pure black for the title, a color that appears nowhere in my palette.

I ran the identical instruction a second time and a different card came back: this one invented a subtitle and a pair of toggle switches the first one didn't have. Same sentence, different product. It drifted further, too: padding of 20, a value that isn't on my spacing scale at all. And my favorite detail in the whole experiment: it filled the toggles with a teal one digit off from my teal token. Invisible on every screen it will ever be shown on. Wrong in the only way that matters.

Then I added one sentence to the instruction: use my variables, styles, and components. Compliance went from zero percent to one hundred. Every meaningful property, connected correctly, first try.

Both halves of that matter. The agent made the same mistake I had made by hand in my button component, faster and at scale: right values, missing connections, invisible in a screenshot. And the thing that flipped it wasn't a technical fix. It was a designer stating a requirement. The agent's default is appearance. The sentence that made it structural was mine.

06

Experiment two: real behavior, no representation

The second experiment came at the seam from the opposite side. I picked a control our product will genuinely need and built it with Claude writing the code, specifically to find where the design system's coverage ends: a picker where the user chooses exactly two accounts to compare, from a list grouped by account type. A designer would spec this in an afternoon, and there are a few reasonable ways to design it: two dropdowns side by side, a capped checklist, a multi-select field.

Start with the most common pattern, two dropdowns. Each dropdown turns out to be its own island. Two separate values, so nothing stops the user picking the same account twice, and no shared sense of "one of two chosen." Detecting the duplicate, closing the loophole, making the pair read as one control: all custom code before the design is even interesting.

The multi-select route surfaced something bigger, and it had nothing to do with the picker. Under every component library sits what engineers call a primitive layer: the code that handles the unglamorous mechanics of an interface, where keyboard focus goes, what Escape does, how a dropdown opens and closes. Shadcn used to sit on one primitive layer; it now lets each team choose between two, and the same component name behaves differently depending on the choice. My product sits on one flavor. A fresh test project, set up with defaults, landed on the other, and the differences showed up immediately. Two teams can both truthfully say "we use the shadcn Select" and be holding components with different capabilities. The name is no longer a spec. If your system documentation says "we use shadcn," it is now underspecified in a way it wasn't a year ago.

On my product's flavor, the stock Select cannot do multiple selection at all, and what happens when you ask it to is the revealing part. Nothing refuses. The placeholder text vanishes. The dropdown closes after the first pick as if nothing were unusual. The second pick replaces the first. Underneath, the component quietly changes the shape of the data it hands your product, from a list of selections to a single one. Nothing tells you plainly; the warnings that do exist are buried in engineering tools, and they're cryptic even there. It warns obscurely, then does the wrong thing. The designer takeaway: the distance between "the design calls for picking two" and "the component can pick two" turned out to be a real engineering effort, not a checkbox.

So the picker got built the long way, and the accounting of that build is the whole point, because the split in it is the seam itself. The finished control is 119 lines of our own code sitting on roughly 750 lines of library plumbing. Everything visual came from the system: chips, rows, group labels, filtering states. Every behavior that makes it this control rather than a generic multi-select is ours alone: the two-pick limit, the greying of remaining rows once two are chosen, the placeholder that walks from "Compare two accounts" to "Pick one more" to "Two selected."

Now the part I got wrong when I first drafted this argument. I wanted to write that the library "fails" because it has no setting for a maximum number of selections. That's backwards, and I caught myself doing it. A component library should not encode product rules. "Exactly two accounts" is product logic, the same way a password length rule doesn't belong to the text field. Defining it in application code is correct engineering, not a workaround.

But follow that correctness to where it lands. The rule now lives in a small function in our codebase. It has no home in the component library, which is right, and no home in Figma, which should worry you. The at-limit state, the greyed rows, the placeholder sequence: real, deliberate, designed behavior, and both halves of the design system are blind to it by construction. I can draw an "at limit" variant in Figma; the rule that produces it exists only in code. So the interesting question stops being "why can't the library do this" and becomes: where does designed behavior get recorded when neither the component library nor the design file can hold it? Today the honest answer is: in a function, guarded by a code review, remembered by whoever wrote it. Which is why this class of decision goes undocumented and gets quietly re-litigated on the next screen that needs it.

07

Why not just tokens?

The token-graph camp deserves a direct answer, because it's the most credible alternative: put every design decision in a shared token file, generate the Figma variables and the code values from it, and neither side can drift. I run the lightweight version myself; it's my second tier.

But look at what the experiments found. In the first, the values were already right, identical to the tokens, and the system still drifted, because the failure was in the connections, and a token file can't see whether anything is connected to it. In the second, the drift was behavior, which no token can express at all. A token graph keeps the palette honest. Both of my failures lived where tokens can't look.

08

The part AI actually changed

Designers have always half-known the code was where truth lived. We stayed away because working at that layer was expensive. That cost is what AI removed, and it's worth being precise about what it removed and what it didn't.

My working loop starts with a brief I write: the jobs the user is completing, the mental model they bring, what the screen has to earn. Claude researches how comparable products handle the problem, then returns three to six fully realized directions, each arriving with its reasoning attached, because I demand the reasoning in the same breath. This is the actual prompt from one of those sessions:

"Kick out 3 options for me to review. But before you do any design, scrape the internet and analyze what competitors and competitor-adjacent companies are doing. Review some of the most beautiful dashboard designs. Then when you're ready to create those layouts, give me the intent behind each design option."

The actual prompt · research first, reasoning required, judgment reserved

The reasoning isn't the machine doing my thinking; it's a review instrument. Options whose rationale doesn't survive contact with my brief get killed, and I change the survivors myself. Where I used to spend a week getting from brief to a wall of lo-fi, I now get high-fidelity, system-compliant directions the same day, and the craft, the last fifty percent, never left my hands.

The first experiment adds the sharpest version of this point. The difference between disconnected decoration and system-compliant structure was one sentence of designer intent. The mechanical layer belongs to the machine now. The requirements are still ours, and stating them precisely turns out to be the whole job.

09

Three edges I didn't test

Honesty about the boundaries, before you spend the time finding them yourself. Platform: this approach lives in web technology. The moment a product has a native iOS surface (SwiftUI) or Android surface (Jetpack Compose), the seam reopens there at full width; you're streamlining one lane of your deliverables, not all of them. Cross-platform toolkits like Flutter change the shape of that tradeoff, not the existence of it. Ownership drift: shadcn copies its components into your codebase, so your team owns them, which also means library improvements don't reach your copy until someone goes and gets them. Who keeps your copy current with the library it came from is a real question with no default answer. The sameness tax: yes, AI tools default to shadcn, and the internet is filling with the same zinc-and-rounded-corners look. But sameness lives in the defaults, not the structure. Everything that makes a product look like itself, type, color, spacing, motion, density, is exactly the layer that stays fully yours, because the primitives were never the design; they're the plumbing under it. If your product looks generic on this foundation, the foundation isn't what failed.

10

What this is not

This is not an argument that AI replaces designers. Both experiments argue the opposite: the agent's default is appearance, and every move that made the work structural, the extra sentence, the killed options, the two-pick rule, was human judgment. That skill got more valuable, not less.

It's also not an argument that designers must become engineers. I didn't learn to ship production code to do this. I made one structural choice about where the design system lives, and the rest is design work as I've always practiced it, minus the translation tax.

The claim is narrower and, I think, more useful: the foundation you choose determines more about the outcome than the skill you apply on top of it. Most of our handoff pain is a foundation problem disguised as process, and we keep hiring process to fix it.

11

The question I can't answer alone

One designer, one system, one web product is a clean experiment and an unfair one. I now know where my own version leaks: connections that look right and aren't, behavior that's real and unrepresentable, a component name that no longer guarantees a component. The full case study, with the artifacts and tradeoffs, is here on this site.

What I can't test is the organizational layer. Where does designed behavior get recorded at scale, when neither Figma nor the component library can hold it? The traditional answer is documentation, a wiki page beside the codebase, but that's a third artifact kept aligned by hand, another translation layer of exactly the kind this essay opened with. Who keeps a team's copy current with the library it came from? What does contributing to a design system look like when the real button lives in a codebase most designers will never open? If you lead design somewhere the seam costs real money, that's the take I'd genuinely like to hear.