0%

2025.04.12

The hardest part of building a design system isn't design

Three attempts. Two failures. The story of how going into the code, not deeper into Figma, was the thing that finally made a design system land.

There’s a version of this story where I built a design system, it worked perfectly, and everyone lived happily ever after. That version doesn’t exist. What actually happened was messier, slower, and I think, more useful to tell.

This is the story of three attempts. Two failures. And what I finally understood on the third try.

The beginning: learning backwards

In 2021, about a year into my role, I was handed an unexpected brief: design work for a wholesale partner, a company much bigger than ours. To get me started, someone passed me a Zeplin file. Their visual language. Their system. My job was to follow it.

I didn’t really know what I was doing yet. I was learning on the fly, which in hindsight is probably the best way to learn anything that matters. I’d just adopted Figma, and I used that Zeplin file as a reference point to build something of our own, a design system for our website.

Around the same time, two things happened that would shape everything that followed.

The first was a course. I enrolled in a design systems bootcamp on Maven run by Molly Helmuth, creator of UI Prep. For the first time, I had a mental model for how a file should actually be structured. Not just aesthetically, but architecturally. It gave me foundations I didn’t know I was missing.

The second was TailwindCSS. Our team adopted it, and rather than treat it as a developer concern, I decided to go deep. I worked through the documentation until I knew just about the entire thing, every class, every pattern, what was easy to build and what wasn’t. Everything I designed from that point had a method of construction in mind. I wasn’t just designing for how something should look; I was designing for how it would be built.

Version one: functional, but shallow

The first design system was simple. Mostly consistent on the basics, buttons, cards, type, but it lacked the polish I’d later come to care about. Accessibility was an afterthought. It encapsulated the brand well enough, but it lived as a reference rather than a foundation. Nobody was building directly from it.

Still, it did its job.

It was robust enough to support the first version of our cart checkout, a product that abandoned our old sequential signup process to let users purchase mobile products alongside NBN at the same time. At the time, we were one of the first in the industry to do this. It was a meaningful piece of work, and the design system, imperfect as it was, held it together.

Simple. Functional. Done. On to the next thing.

Version two: the over-engineering trap

At the start of 2023, we went through a rebrand. And I saw an opening.

By this point I had spent two years deep in design Twitter, following designers from the likes of Linear, Tailwind, Clerk, and Stripe. I was watching people who cared about the craft at a level I hadn’t encountered before. And I got swept up in it.

Every emerging trend became something I had to try. Layers of inner shadows and skeuomorphism. Opaque gradient backgrounds with backdrop blur for glassmorphism. Custom UI illustrations. I even tried to manually replicate animated assets, something I later discovered had already been open sourced in Magic UI, which tells you something about where my perspective was at. The details felt like mastery. What I was actually doing was losing the plot.

I wanted to build something that could sit alongside the systems I admired. I loved the elegance of Carbon by IBM, the polish of Geist by Vercel, the considered token usage of Atlassian, and the tooling depth of Primer by GitHub. Atlassian had public Figma files too, so I went in and worked through how their components were constructed and admired how thoroughly they documented everything. Each system raised the bar for what I thought was possible.

The rebrand was the moment to make something worthy of that ambition. Looking back, that framing was already the problem.

What I built was technically impressive. I over-utilised slot components, setting up grids and columns with slots nested inside slots, components nested inside components, variants and booleans everywhere. I introduced utility classes with full dark and light mode support, Figma variables had just dropped, and I was one of the first in the team to use them seriously. Dark mode wasn’t the real goal though. The rebrand had been an enormous manual effort, and I saw variables as the thing that would make the next one vastly more efficient. Future-proofing through tokens. I accessibility-checked everything. I built extensive tables mapping which colours were accessible on which backgrounds. I wanted every element in the library, even ones we hadn’t needed yet, because I wanted it to be comprehensive.

I was proud of it.

And then I tried to advocate for code-design alignment around the utility classes, a way to make sure what lived in Figma corresponded directly to how developers were building in Tailwind.

The response I got was direct: “Don’t tell them how to do their job.”

That stung. And honestly, they weren’t entirely wrong to push back, not because utility class alignment was a bad idea, but because I’d buried the idea under so much complexity that it was hard to see the value through the noise. The flair probably scared them off before the conversation even started. The failure wasn’t just in communication, it was in the system itself.

I hadn’t learned how to advocate, but more than that: I hadn’t yet learned to design for the people who would have to build it.

The turning point: going into the code

After that, I made a decision that changed everything.

Instead of continuing to study design systems, visual systems, token structures, Figma file organisation, I started studying code.

Specifically, open source UI component libraries. The kind built by engineers, for engineers, to a standard as good as anything you’d find at a major company, and available to anyone who wanted to look.

I pulled repositories to my machine and went through the file structures. I took particularly well to shadcn/ui, it was gaining momentum fast, built entirely for free, and structured in a way that made the relationship between design decisions and implementation unusually transparent. I’d pull the repo, work through the component structure, and then design components to match it. shadcn has since become something close to an industry standard, and having spent that much time inside it early gave me a fluency that would prove useful later in ways I didn’t anticipate.

Alongside shadcn I worked through Radix UI, Headless UI, Nuxt UI, TailwindCSS UI, and Prime UI. Each had something different to offer.

What this practice showed me, more than anything else, was what is and isn’t practical to build. Not in theory. In reality. When you’re staring at actual component code, you start to understand why certain design decisions exist and why others are impossible to implement without enormous cost.

The accessibility-focused libraries, Radix especially, sent me down another path entirely. I started absorbing the W3C APG patterns, the accessibility rules that govern how interactive components should behave. And I began to understand something I’d been dismissing: why so many native components exist, and why fighting them is often the wrong instinct. Constraints are sometimes features in disguise.

I was learning to design less like an artist and more like a collaborator.

Version three: restraint as sophistication

In 2025, another brand adjustment arrived, and with it, another large manual task: updating colour values across the entire product. I raised the case for utility classes and variable alignment again. This time, the reception was warmer. The conversation had changed, partly because I had changed.

Figma’s variable modes had just expanded beyond four, which opened up far more of what I’d been imagining for years. I could build variable sets that covered product data, theming, and responsiveness in ways that had previously been impossible. It was time to start again. For the third time.

But this version felt different from the moment I started.

I wasn’t trying to prove anything. The need to flex had worked its way out of my system across the previous years of failure, iteration, and honest self-reflection. What I wanted now was the opposite of what I’d chased before: to do as much as possible with the fewest number of parts. And more than anything, I just wanted it built.

I cut the colour palette significantly. Three colours became one through hue shifting. I designed components the way I now understood they needed to be built, practical, efficient, respectful of the developers’ time. I didn’t design what we didn’t need. I started small and built outward slowly. I kept everything together rather than splitting files apart until performance genuinely required it, which made the system far easier to navigate.

Most importantly: I brought the development team in from the beginning.

Not as an audience. As collaborators. I checked in constantly, through design, through early builds, through to checkout, to make sure I was working toward something realistic rather than something aspirational. When their feedback required us to meet the code where it was, we moved. Both sides adjusted. That mutual respect was new, and it made all the difference.

The system became something they were genuinely eager to work with. And when you build something developers are eager to work with, alignment stops being something you have to argue for, it becomes something that happens naturally.

What we built

Over a series of months, we achieved something I’d tried and failed to reach twice before: total alignment between Figma and the codebase.

Variables handled product variants, responsiveness, and theming. Prototypes and screens came together rapidly. Our small team consistently stayed ahead of development, which meant Figma remained a genuine source of truth, not just a reference point that developers interpreted.

What’s in Figma is what’s on the website.

That sounds simple. It is anything but. When those two things align, stakeholders can comment on designs before a single line of production code is written. Post-development changes become minimal. Handoff becomes a conversation rather than a translation exercise. The whole team moves faster, with less friction, and more confidence.

What I actually learned

Version one taught me that something functional is worth shipping, even if it isn’t perfect.

Version two taught me that impressive and useful are not the same thing, and that technical sophistication without organisational alignment is just noise.

The turning point wasn’t a better Figma skill or a sharper design eye. It was going into the code. Understanding how things are actually built changed how I designed everything that followed. It gave me a language that development teams could hear.

Version three succeeded not because I was more talented, but because I was more humble. I stopped designing for the version of the product I imagined and started designing for the team that had to build it. I stopped trying to match the systems I admired from the outside and started building something that worked from the inside out.

Looking back across four years and three attempts, what strikes me most is how necessary each failure was. The over-engineering of version two wasn’t wasted time, it was the thing that sent me into the codebases, which was the thing that changed everything. The rejection of the utility class proposal stung, but it forced me to ask harder questions about why I wasn’t being heard. Every dead end pointed somewhere.

I started this journey reverse-engineering someone else’s Zeplin file with no idea what I was doing. I finished it with a system that developers were eager to build from, a Figma file that matched the website, and a much clearer understanding of what design systems are actually for.

They’re not portfolios. They’re not proof of skill. They’re infrastructure, and infrastructure only works when the people who have to live inside it helped shape it.

Three design systems. Two failures. One lesson that runs through all of it:

The best design isn’t the most impressive design. It’s the one that actually gets built.

← Back to home