Design Systems
Make the Right Thing the Easy Thing
Design systems don’t fail on component quality. They fail on adoption, and adoption is an architecture problem before it’s a persuasion problem.

Most design systems fail the same way. They ship a component library, adoption stalls around 40%, and the system quietly becomes a suggestion.
I’ve come to think that happens because we treat adoption as a persuasion problem. We write the guidelines, run the roadshow, and hope teams choose the system. But teams under deadline pressure don’t choose the system. They choose whatever is fastest. The only reliable fix is to make the system the fastest path.
I learned this building a design system for an enterprise product suite that had grown through a series of acquisitions. Each acquired product arrived with its own interface conventions, its own component patterns, and its own idea of what a button looked like. At the same time, the company was rebranding, so every surface was about to be touched anyway.
The problem isn’t inconsistency
The obvious version of the problem is inconsistency: different teams shipping different buttons. The real version is more expensive.
Without a shared component layer, every team solves accessibility on its own, which means most solve it badly or not at all. The same component gets built three times, and the three versions diverge immediately. Design-to-development handoff becomes a translation exercise, because there’s no shared vocabulary to hand off to. Onboarding a new engineer means learning one team’s conventions instead of one company’s. And every acquisition compounds all of it.
We also had a complication most design systems don’t: two front-end frameworks, React and Angular, both in production and neither going away.
So the core question wasn’t “what should our components look like?” It was “what has to be true so that doing the right thing is also the easy thing?”
Six decisions that did the work
One foundational primitive, and everything composes from it. The base component is a polymorphic Box that can render as any element while keeping its styling contract and type safety. Every other component is built on top of it. Consistency became structural instead of enforced: you get the spacing scale and the type system whether you want them or not, and new components are cheap to build because the foundation already handles layout and spacing. The trade-off is a steeper learning curve for engineers used to writing components from scratch. It was worth it, but it was real.
Build on Radix primitives, not from scratch. Interaction-heavy components like checkboxes, dialogs and popovers sit on Radix UI primitives. Keyboard navigation, focus management and ARIA semantics come out correct by default instead of being reimplemented and subtly broken each time. Our effort went into the design language instead of rebuilding a dialog that already exists. The trade-off is a dependency on someone else’s release cadence.
Zero-runtime stylesheets, precompiled with TypeScript. Styles are compiled at build time, with the type system covering the styling API. There’s no runtime style cost on the data-heavy screens where enterprise users actually spend their day. Design tokens became type-checked, so a typo in a color name fails the build instead of shipping. Theming, including light, dark and high contrast, resolves at build time rather than being layered on. We gave up some runtime flexibility, which for an enterprise suite is the right side of the trade.
Two frameworks, one design language. This is the hardest part of the work, and the part most design systems never attempt. We made the system framework-agnostic at the token and contract layer, instead of pretending one framework could serve both. A designer specifies something once and both stacks understand it, and teams can move between stacks without relearning the vocabulary.
Accessibility as structure, not a checklist. ARIA support, focus behavior and semantic markup live inside the components, and theme-aware modes are built into the system rather than applied over it. Every new page built on the system ships at WCAG 2.1 AA by default. That turned accessibility from a remediation backlog into a prevention model. (I wrote about that program in a separate post, but the two are really the same work at different altitudes.)
Public, versioned documentation. We documented the system with a full component reference and separate Types, Hooks and Utils surfaces. Documentation is a product, not an afterthought: if it isn’t findable, it isn’t adopted. Exposing Hooks and Utils also meant teams could build system-consistent components we hadn’t shipped, instead of going around the system.
What shipped
The system launched with 42 components, from primitives like Box, Flex and Stack up to a data grid built for unlimited data sets. It has two framework implementations sharing one token layer, full TypeScript coverage across the styling and component APIs, and light, dark and high contrast theming. Each component page has usage and accessibility guidance.
It was adopted by more than five scrum teams. Design-to-development handoff cycles dropped by about 40%, and support ticket volume fell by about 15% across the re-architected applications.
What I took from it
Design systems don’t fail on component quality. They fail on adoption, and adoption is an architecture problem before it’s a persuasion problem.
Every decision here aimed at the same thing: making the correct path also the easiest one. Compose from Box because it’s less work than not. Get accessibility for free because it’s built into the primitive. Use the tokens because the build fails otherwise.
By the time we ran a formal accessibility program, the system had already made most of the problem structural. That wasn’t luck. It was the point.