Skip to content

Owning the roadmap for Ubiquiti's design system

Built for one application and borrowed awkwardly by everyone else. I made the system agnostic, rebuilt it on React Aria, and grew it from five consuming applications to more than 35.

Role
Lead Design System Engineer, then Technical Product Manager
Organisation
Ubiquiti
Period
2022 to now
Disciplines
  • Design systems
  • Roadmap
  • Accessibility
  • Developer tooling
  • Testing
  • Platform adoption
Consuming applications
35+
Adoption across them
100%
Engineers on the team
2 to 5

The design system was built for one application. Other teams could use it, but they had to bend it first, and the work of bending it fell to them. Two people maintained it. The components were old, difficult to change and had not been built with accessibility in mind. Everyone downstream paid for that.

What is not here

This describes an internal system, so it names no products, teams or people, and it leaves out figures I am not free to share. Where a detail is missing it is missing on purpose rather than because I do not have it. I can go further in conversation.

Context

The system was a component library and not much else. Tokens barely existed. The Figma library had drifted a long way from what the components actually looked like and how they behaved, so designers and engineers were working from different sources and both had reason to think they were right.

The deeper problem was ownership. The system was built for, and owned by, a single product team, and it lived inside that team. It served that team well. Five applications consumed it, and the four that were not the owner were second in line by construction. What the owning team needed went in. What anyone else needed waited.

I did not start on the design system. I joined Ubiquiti building front end for a product that consumed one, which is where my sense of what a system owes its consumers came from. I led the rebuild as lead design system engineer, then took the roadmap as technical product manager.

The problem

Three costs, and all of them landed on people other than us.

The components were not accessible. Not partly, and not in the places that are easy to miss: accessibility had not been an input at all. So every consuming team either shipped the defect or fixed it locally, again.

The code was old and tangled. That made every change slow, and made some changes too risky to attempt at all.

Consistency did not exist in any enforceable sense. Two applications solving the same problem looked and behaved differently, and nothing in the process noticed. Delivery was slow, and components broke in cases nobody had thought about.

What I owned

The roadmap, the architecture and the team. I hired the developers myself, handpicked out of thousands of applications, and grew the team from two to five. All five are still here, which I count as an outcome rather than a detail.

The first structural decision was to move the system out of the product team that owned it and make it agnostic, with no privileged consumer. That removed the mechanism by which one team's needs outranked everyone else's, rather than asking people to stop using it.

Figma and code are now held at parity, and kept there by working with the designers who use the library rather than by publishing guidelines at them.

No product team reports to me, so I cannot assign priority. Alignment has to be earned, and the most reliable way I have found to earn it is to make the aligned path the easier one. That means the tooling is part of the product, not a side effect of it:

  • A component MCP server, so an agent working inside a consuming codebase uses the library correctly, with migrations it can run itself.
  • Default copy with translations built in, so text is not re-solved once per application.
  • A copy tool that helps consumers write to the guidelines instead of guessing at them.
  • A chart builder, so data visualisation stops being rebuilt from scratch.
  • An audit and ticketing system that grades every consuming application on consistency and adherence each quarter, and tracks each deviation until it is closed.

The company moved quickly on agentic coding and my team moved earlier than most, which is why several of those tools exist at all. A design system is unusually well placed for this: it is the one library that knows what correct looks like.

I also pushed for the testing, which is the least visible part of the work and the part the rest of it rests on. Every component is covered by unit tests, browser tests and Storybook tests, and so is every composition of them, which matters more: components rarely break alone, they break in combination. Where that still left a gap was appearance, so we built our own visual snapshot tool to catch regressions a passing assertion will not, in the territory Chromatic occupies.

None of that is interesting in itself. It is the reason a fix can ship the same day. Without it, every change to a library with more than 35 consumers is a question nobody can answer quickly, so the safe move is to ship nothing, and a design system that is slow to change gets worked around rather than used.

I did not use ownership to refuse work. When a consuming team brought something that did not fit, taking it and reshaping it until it did was almost always the more useful move.

A decision and its trade-off

The old components had to go. They could not be made accessible by patching, and their age was the reason change was slow. So the open question was not whether to rebuild, it was what to rebuild on.

Accessibility was the binding constraint. It was the thing the old library got most wrong, and the thing consuming teams were least equipped to fix for themselves. That ruled out any option where accessibility would be ours to solve from first principles.

Trade off

What should the rebuilt component library be built on?

Options considered
  • Radix
  • Headless UI
  • Web components
  • Fully custom
  • React Ariachosen
What I chose
React Aria
What it cost
React Aria's API is opinionated. Where our requirements did not match its model we had to work around it, and tooltips are the clearest case. The rebuild was also developer heavy, and heaviest on the largest components, so there was a long stretch where the team's effort was going into parity rather than into anything new.

The cost landed where I expected it, on us rather than on consumers. The workarounds are contained and documented, and tooltips are still the one I would point at first. The rebuild was heavy and it stayed heavy longer than I would have liked on the biggest components.

React Aria earned the place on three things: accessibility, mobile support, and an API our consumers liked working in. The third is not a footnote. Adoption is a much easier argument when what you are asking teams to move to is better to work with than what they already have. Each year since, the feedback from consuming teams has been that it was the system's best year so far.

Accessibility as an input

The clearest example is the architecture itself.

The library is split in two. Recipes are drop-in components with the decisions already made, accessibility included. Primitives are the building blocks, sometimes hooks, that Recipes are composed from, and they exist for the cases where an application genuinely needs a different design.

That split is a decision about where responsibility for accessibility sits. A team reaching for a Recipe gets correct behaviour without needing to know why it is correct. A team reaching for a Primitive is taking that responsibility on, and knows it is doing so.

The honest part is what happened next. Flexibility was used more than the design assumed, and the deviations accumulated. A Primitive is an invitation to build something bespoke, and enough teams accepted it that consistency started to erode from a new direction: not because the library was hard to use, but because the way around it was easy.

So we built the audit system. It tracks the builds that deviate from a Recipe, reports them, and grades every application on consistency and adherence each quarter. 34 regressions have been resolved through it. It did not close the escape hatch, which would have traded one problem for another. It made using the escape hatch visible, and that turned out to be enough.

Outcomes

Regressions resolved
34
A fix ships in
Under a day
A feature ships in
Under a week

Adoption is the figure I care about most, because it is the one that cannot be argued with. The system went from five consuming applications to more than 35, and there is nothing still running on the legacy library.

Delivery changed shape. Fixes ship in under a day and new features in under a week, against a library where some changes had previously been too risky to attempt. The test coverage is what makes that speed safe rather than reckless.

Every consuming application is audited each quarter and graded on consistency and adherence, so drift is caught on a schedule rather than noticed by whoever happens to look.

What I would do differently

I would have fixed the design sync sooner.

Keeping designers aligned with the guidelines was a struggle for a long time. The fix was to hire a UX designer from a larger office whose remit included holding that alignment, and it worked. Getting there took longer than the problem deserved, and most of the delay was bureaucratic rather than any doubt about whether it was the right move.

The delay had a cost. Designers and engineers carried on drifting apart, which is the specific failure a design system exists to prevent, and it is the one I was slowest to move on.

What I take from it is that an organisational gap is worth escalating at the point you can describe it, not at the point you can prove it. I could describe this one long before I acted on it, so the distance between describing a problem and moving on it is now the thing I watch for in my own work.